module Alchemy module Admin # This module contains helper methods for rendering dialogs, toolbar buttons and confirmation windows. # # The most important helpers for module developers are: # # * {#toolbar} # * {#toolbar_button} # * {#link_to_dialog} # * {#link_to_confirm_dialog} # module BaseHelper include Alchemy::BaseHelper include Alchemy::Admin::NavigationHelper # Returns a string showing the name of the currently logged in user. # # In order to represent your own +User+'s class instance, # you should add a +alchemy_display_name+ method to your +User+ class # def current_alchemy_user_name name = current_alchemy_user.try(:alchemy_display_name) if name.present? content_tag :span, "#{Alchemy.t('Logged in as')} #{name}", class: 'current-user-name' end end # This helper renders the link to an dialog. # # We use this for our fancy modal dialogs in the Alchemy cockpit. # # == Example # # <%= link_to_dialog('Edit', edit_product_path, {size: '200x300'}, {class: 'icon_button'}) %> # # @param [String] content # The string inside the link tag # @param [String or Hash] url # The url of the action displayed inside the dialog. # @param [Hash] options # options for the dialog. # @param [Hash] html_options # HTML options passed to the link_to helper # # @option options [String] :size # String with format of "WidthxHeight". I.E. ("420x280") # @option options [String] :title # Text for the dialog title bar. # @option options [Boolean] :modal (true) # Show as modal window. # def link_to_dialog(content, url, options = {}, html_options = {}) default_options = {modal: true} options = default_options.merge(options) link_to content, url, html_options.merge('data-alchemy-dialog' => options.to_json) end # Used for translations selector in Alchemy cockpit user settings. def translations_for_select Alchemy::I18n.available_locales.map do |locale| [Alchemy.t(locale, scope: :translations), locale] end end # Used for site selector in Alchemy cockpit. def sites_for_select Alchemy::Site.all.map do |site| [site.name, site.id] end end # Returns a javascript driven live filter for lists. # # The items must have a html +name+ attribute that holds the filterable value. # # == Example # # Given a list of items: # # <%= js_filter_field('#products .product') %> # # # # @param [String] items # A jquery compatible selector string that represents the items to filter # @param [Hash] options # HTML options passed to the input field # # @option options [String] :class ('js_filter_field') # The css class of the tag # @option options [String or Hash] :data ({'alchemy-list-filter' => items}) # A HTML data attribute that holds the jQuery selector that represents the list to be filtered # def js_filter_field(items, options = {}) options = { class: 'js_filter_field', data: {'alchemy-list-filter' => items} }.merge(options) content_tag(:div, class: 'js_filter_field_box') do concat text_field_tag(nil, nil, options) concat render_icon(:search) concat link_to('', '', class: 'js_filter_field_clear', title: Alchemy.t(:click_to_show_all)) concat content_tag(:label, Alchemy.t(:search), for: options[:id]) end end # Returns a link that opens a modal confirmation to delete window. # # === Example: # # <%= link_to_confirm_dialog('delete', 'Do you really want to delete this comment?', '/admin/comments/1') %> # # @param [String] link_string # The content inside the tag # @param [String] message # The message that is displayed in the dialog # @param [String] url # The url that gets opened after confirmation (Note: This is an Ajax request with a method of DELETE!) # @param [Hash] html_options # HTML options get passed to the link # # @option html_options [String] :title (Alchemy.t(:please_confirm)) # The dialog title # @option html_options [String] :message (message) # The message displayed in the dialog # @option html_options [String] :ok_label (Alchemy.t("Yes")) # The label for the ok button # @option html_options [String] :cancel_label (Alchemy.t("No")) # The label for the cancel button # def link_to_confirm_dialog(link_string = "", message = "", url = "", html_options = {}) link_to(link_string, url, html_options.merge( 'data-alchemy-confirm-delete' => { title: Alchemy.t(:please_confirm), message: message, ok_label: Alchemy.t("Yes"), cancel_label: Alchemy.t("No") }.to_json ) ) end # Returns a form and a button that opens a modal confirm dialog. # # After confirmation it proceeds to send the form's action. # # === Example: # # <%= button_with_confirm('pay', '/admin/orders/1/pay', message: 'Do you really want to mark this order as payed?') %> # # @param [String] value # The content inside the tag # @param [String] url # The url that gets opened after confirmation # @param [Hash] options # Options for the Alchemy confirm dialog (see also +app/assets/javascripts/alchemy/alchemy.confirm_dialog.js.coffee+) # @param [Hash] html_options # HTML options that get passed to the +button_tag+ helper. # # @note The method option in the html_options hash gets passed to the form_tag helper! # def button_with_confirm(value = "", url = "", options = {}, html_options = {}) options = { message: Alchemy.t(:confirm_to_proceed), ok_label: Alchemy.t("Yes"), title: Alchemy.t(:please_confirm), cancel_label: Alchemy.t("No") }.merge(options) form_tag url, {method: html_options.delete(:method), class: 'button-with-confirm'} do button_tag value, html_options.merge('data-alchemy-confirm' => options.to_json) end end # A delete button with confirmation window. # # @option title [String] # The title for the confirm dialog # @option message [String] # The message for the confirm dialog # @option icon [String] # The icon class for the button # def delete_button(url, options = {}, html_options = {}) options = { title: Alchemy.t('Delete'), message: Alchemy.t('Are you sure?'), icon: 'destroy' }.merge(options) button_with_confirm( render_icon(options[:icon]), url, { message: options[:message] }, { method: 'delete', title: options[:title], class: "icon_only #{html_options.delete(:class)}".strip }.merge(html_options) ) end # (internal) Renders translated Module Names for html title element. def render_alchemy_title if content_for?(:title) title = content_for(:title) else title = Alchemy.t(controller_name, scope: :modules) end "Alchemy CMS - #{title}" end # (internal) Returns max image count as integer or nil. Used for the picture editor in element editor views. def max_image_count return nil if !@options image_count = @options[:maximum_amount_of_images] || @options[:max_images] image_count.blank? ? nil : image_count.to_i end # Renders a toolbar button for the Alchemy toolbar # # == Example # # <%= toolbar_button( # icon: 'create', # label: 'Create', # url: new_resource_path, # title: 'Create Resource', # hotkey: 'alt+n', # dialog_options: { # title: 'Create Resource', # size: "430x400" # }, # if_permitted_to: [:create, resource_model] # ) %> # # @option options [String] :icon # Icon class. See +app/assets/stylesheets/alchemy/icons.css.sccs+ for available icons, or make your own. # @option options [String] :label # Text for button label. # @option options [String] :url # Url for link. # @option options [String] :title # Text for title tag. # @option options [String] :hotkey # Keyboard shortcut for this button. I.E +alt-n+ # @option options [Boolean] :dialog (true) # Open the link in a modal dialog. # @option options [Hash] :dialog_options # Overlay options. See link_to_dialog helper. # @option options [Array] :if_permitted_to ([:action, :controller]) # Check permission for button. Exactly how you defined the permission in your +authorization_rules.rb+. Defaults to controller and action from button url. # @option options [Boolean] :skip_permission_check (false) # Skip the permission check. NOT RECOMMENDED! # @option options [Boolean] :loading_indicator (true) # Shows the please wait dialog while loading. Only for buttons not opening an dialog. # def toolbar_button(options = {}) options = { dialog: true, skip_permission_check: false, active: false, link_options: {}, dialog_options: {}, loading_indicator: true }.merge(options.symbolize_keys) button = render( 'alchemy/admin/partials/toolbar_button', options: options ) if options[:skip_permission_check] || can?(*permission_from_options(options)) button else "" end end # Renders the toolbar shown on top of the records. # # == Example # # <% label_title = Alchemy.t("Create #{resource_name}", default: Alchemy.t('Create')) %> # <% toolbar( # buttons: [ # { # icon: 'create', # label: label_title, # url: new_resource_path, # title: label_title, # hotkey: 'alt+n', # dialog_options: { # title: label_title, # size: "430x400" # }, # if_permitted_to: [:create, resource_model] # } # ] # ) %> # # @option options [Array] :buttons ([]) # Pass an Array with button options. They will be passed to {#toolbar_button} helper. # @option options [Boolean] :search (true) # Show searchfield. # def toolbar(options = {}) defaults = { buttons: [], search: true } options = defaults.merge(options) content_for(:toolbar) do content = <<-CONTENT #{options[:buttons].map { |button_options| toolbar_button(button_options) }.join} #{render('alchemy/admin/partials/search_form', url: options[:search_url]) if options[:search]} CONTENT content.html_safe end end # (internal) Used by upload form def new_asset_path_with_session_information(asset_type) session_key = Rails.application.config.session_options[:key] if asset_type == "picture" alchemy.admin_pictures_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token, :format => :js) elsif asset_type == "attachment" alchemy.admin_attachments_path(session_key => cookies[session_key], request_forgery_protection_token => form_authenticity_token, :format => :js) end end # Renders a textfield ready to display a datepicker # # Uses a HTML5 ++ field. # A Javascript observer converts this into a fancy Datepicker. # If you pass +'datetime'+ as +:type+ the datepicker will also have a Time select. # # The date value gets localized via +I18n.l+. The format on Time and Date is +datepicker+ # or +datetimepicker+, if you pass another +type+. # # === Date Example # # <%= alchemy_datepicker(@person, :birthday) %> # # === Datetime Example # # <%= alchemy_datepicker(@page, :public_on, type: 'datetime') %> # # @param [ActiveModel::Base] object # An instance of a model # @param [String or Symbol] method # The attribute method to be called for the date value # # @option html_options [String] :type (date) # The type of text field # @option html_options [String] :class (type) # CSS classes of the input field # @option html_options [String] :value (value of method on object) # The value the input displays. If you pass a String its parsed with +Time.parse+ # def alchemy_datepicker(object, method, html_options = {}) type = html_options.delete(:type) || 'date' date = html_options.delete(:value) || object.send(method.to_sym).presence date = Time.zone.parse(date) if date.is_a?(String) value = date ? l(date, format: "#{type}picker".to_sym) : nil text_field object.class.name.demodulize.underscore.to_sym, method.to_sym, {type: type, class: type, value: value}.merge(html_options) end # Merges the params-hash with the given hash def merge_params(p = {}) params.merge(p).delete_if { |_k, v| v.blank? } end # Deletes one or several params from the params-hash and merges some new params in def merge_params_without(excludes, p = {}) current_params = params.clone.symbolize_keys if excludes.is_a?(Array) excludes.map { |i| current_params.delete(i.to_sym) } else current_params.delete(excludes.to_sym) end current_params.merge(p).delete_if { |_k, v| v.blank? } end # Deletes all params from the params-hash except the given ones and merges some new params in def merge_params_only(includes, p = {}) current_params = params.clone.symbolize_keys if includes.is_a?(Array) symbolized_includes = includes.map(&:to_sym) current_params.delete_if { |k, _v| !symbolized_includes.include?(k) } else current_params.delete_if { |k, _v| k != includes.to_sym } end current_params.merge(p).delete_if { |_k, v| v.blank? } end def render_hint_for(element) return unless element.has_hint? link_to '#', class: 'hint' do render_icon(:hint) + content_tag(:span, element.hint.html_safe, class: 'bubble') end end # Appends the current controller and action to body as css class. def alchemy_body_class "#{controller_name} #{action_name}" end # (internal) Returns options for the clipboard select tag def clipboard_select_tag_options(items) if @page.persisted? && @page.can_have_cells? grouped_options_for_select(grouped_elements_for_select(items, :id)) else options = items.map do |item| [item.respond_to?(:display_name_with_preview_text) ? item.display_name_with_preview_text : item.name, item.id] end options_for_select(options) end end # Returns the regular expression used for external url validation in link dialog. def link_url_regexp Alchemy::Config.get(:format_matchers)['link_url'] || /^(mailto:|\/|[a-z]+:\/\/)/ end private def permission_from_options(options) if options[:if_permitted_to].blank? options[:if_permitted_to] = permission_array_from_url(options) else options[:if_permitted_to] end end def permission_array_from_url(options) action_controller = options[:url].gsub(/\A\//, '').split('/') [ action_controller.last.to_sym, action_controller[0..action_controller.length - 2].join('_').to_sym ] end end end end