Module: ApplicationHelper

Constant Summary

Constants included from Www::SeoHelper

Www::SeoHelper::AWARDS, Www::SeoHelper::CA_ADDRESS, Www::SeoHelper::CA_BUSINESS_HOURS, Www::SeoHelper::CA_CONTACT_POINT, Www::SeoHelper::CA_CURRENCIES, Www::SeoHelper::CA_DESCRIPTION, Www::SeoHelper::CA_FOUNDING_DATE, Www::SeoHelper::CA_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::CA_LEGAL_NAME, Www::SeoHelper::CA_LOCAL_BUSINESS, Www::SeoHelper::CA_ONLINE_STORE, Www::SeoHelper::CA_RETURN_POLICY, Www::SeoHelper::CA_SALES_DEPARTMENT, Www::SeoHelper::CA_SERVICE_AREA, Www::SeoHelper::CA_URL, Www::SeoHelper::CA_VAT_ID, Www::SeoHelper::CA_WAREHOUSE_DEPARTMENT, Www::SeoHelper::CA_WAREHOUSE_HOURS, Www::SeoHelper::COMPANY_EMAIL, Www::SeoHelper::COMPANY_LOGO, Www::SeoHelper::COMPANY_NAME, Www::SeoHelper::COMPANY_SLOGAN, Www::SeoHelper::EXPERTISE, Www::SeoHelper::FAX_NUMBER, Www::SeoHelper::GS1_COMPANY_PREFIX, Www::SeoHelper::ISO6523_CODE, Www::SeoHelper::PAYMENT_METHODS, Www::SeoHelper::PHONE_NUMBER, Www::SeoHelper::PRIMARY_NAICS, Www::SeoHelper::REFUND_TYPE, Www::SeoHelper::RETURN_FEES, Www::SeoHelper::RETURN_METHOD, Www::SeoHelper::RETURN_POLICY_CATEGORY, Www::SeoHelper::SECONDARY_NAICS, Www::SeoHelper::SOCIAL_PROFILES, Www::SeoHelper::US_ADDRESS, Www::SeoHelper::US_BUSINESS_HOURS, Www::SeoHelper::US_CONTACT_POINT, Www::SeoHelper::US_CURRENCIES, Www::SeoHelper::US_DESCRIPTION, Www::SeoHelper::US_FOUNDING_DATE, Www::SeoHelper::US_GLOBAL_LOCATION_NUMBER, Www::SeoHelper::US_IMAGE, Www::SeoHelper::US_LEGAL_NAME, Www::SeoHelper::US_LOCAL_BUSINESS, Www::SeoHelper::US_ONLINE_STORE, Www::SeoHelper::US_RETURN_POLICY, Www::SeoHelper::US_SALES_DEPARTMENT, Www::SeoHelper::US_SERVICE_AREA, Www::SeoHelper::US_TAX_ID, Www::SeoHelper::US_URL, Www::SeoHelper::US_WAREHOUSE_DEPARTMENT, Www::SeoHelper::US_WAREHOUSE_HOURS

Constants included from IconHelper

IconHelper::CUSTOM_ICON_MAP, IconHelper::CUSTOM_SVG_DIR, IconHelper::DEFAULT_FAMILY

Instance Method Summary collapse

Methods included from UppyUploaderHelper

#file_uploader, #image_uploader, #large_file_uploader_s3, #lead_sketch_uploader, #rma_image_uploader, #rma_image_uploader_s3, #uppy_uploader, #video_uploader

Methods included from Www::ImagesHelper

#image_asset_tag, #image_asset_url

Methods included from Www::SeoHelper

#add_page_schema, #canada?, #company_social_links, #ensure_context_json, #json_ld_script_tag, #local_business_schema, #online_store_id, #online_store_schema, #page_main_entity, #page_main_entity_json, #render_auto_collection_page_schema, #render_collection_page_schema, #render_local_business_schema, #render_online_store_schema, #render_page_schemas, #render_page_video_schemas, #render_webpage_schema, #render_webpage_schema_with_collections, #usa?

Methods included from UrlsHelper

#catalog_breadcrumb_links, #catalog_link, #catalog_link_for_product_line, #catalog_link_for_sku, #cms_link, #delocalized_path, #path_to_sales_product_sku, #path_to_sales_product_sku_for_product_line, #path_to_sales_product_sku_for_product_line_slug, #product_line_from_catalog_link, #protocol_neutral_url, #sanitize_external_url, #valid_external_url?

Methods included from IconHelper

#account_nav_icon, #fa_icon, #star_rating_html

Instance Method Details

#better_number_to_currency(number, options = {}) ⇒ Object



94
95
96
97
98
99
# File 'app/helpers/application_helper.rb', line 94

def better_number_to_currency(number, options = {})
  return unless number

  strip_insignificant_zeros = ((number * 100) % 100).zero? # e.g 209.00 = 20900 % 100 = 0 therefore we can strip zero. but 209.20 we wouldn't want to render as 209.2
  number_to_currency(number, options.merge(strip_insignificant_zeros:, precision: 2))
end

#check_force_logoutObject



243
244
245
246
247
248
249
# File 'app/helpers/application_helper.rb', line 243

def check_force_logout
  return unless current_user && current_user.force_logout?

  current_user.update_column(:force_logout, false)
  sign_out(current_user.)
  redirect_to request.original_url and return true
end

#check_or_cross(value, options = {}) ⇒ Object

Truthy = green check mark other wise red stop



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'app/helpers/application_helper.rb', line 120

def check_or_cross(value, options = {})
  if value.to_b
    options[:title] = 'Yes'
    options[:class] ||= 'text-green'
    options[:style] ||= 'color:green;font-size:1.2em'
    check_icon = options.delete(:check_icon) || 'check-circle'
    fa_icon(check_icon, **options)
  else
    options[:title] = 'No'
    options[:class] ||= 'text-red'
    options[:style] ||= 'color:red;font-size:1.2em'
    cross_icon = options.delete(:cross_icon) || 'ban'
    fa_icon(cross_icon, **options)
  end
end

#check_or_times(value, _options = {}) ⇒ Object



136
137
138
# File 'app/helpers/application_helper.rb', line 136

def check_or_times(value, _options = {})
  check_or_cross(value, cross_icon: 'times')
end

#error_messages(object) ⇒ Object



185
186
187
188
189
190
191
192
# File 'app/helpers/application_helper.rb', line 185

def error_messages(object)
  return unless object.errors.any?

   :div, class: 'card bg-danger' do
    (:div, "#{pluralize(object.errors.count, 'error')} prohibited this record from being saved:", class: 'card-title') +
      render_error_messages_list(object)
  end
end

#general_disclaimer_on_product_installation_and_local_codesObject



239
240
241
# File 'app/helpers/application_helper.rb', line 239

def general_disclaimer_on_product_installation_and_local_codes
  GENERAL_DISCLAIMER_ON_PRODUCT_INSTALLATION_AND_LOCAL_CODES
end

#gridjs_from_html_table(table_id, **options) ⇒ Object



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'app/helpers/application_helper.rb', line 75

def gridjs_from_html_table(table_id, **options)
  config = {
    search: false,
    pagination: false,
    sort: true,
    from: "##{table_id}"
  }.merge(options)

  stimulus_data = {
    controller: 'gridjs',
    gridjs_autoload_value: true,
    gridjs_config_value: config.to_json
  }

  (:div, data: stimulus_data, class: 'gridjs-wrapper') do
    (:div, '', data: { gridjs_target: 'table' })
  end
end

#gridjs_table(id, data: nil, columns: nil, **options) ⇒ Object

Grid.js helper methods for modern table functionality



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'app/helpers/application_helper.rb', line 53

def gridjs_table(id, data: nil, columns: nil, **options)
  config = {
    search: false,
    pagination: false,
    sort: true,
    resizable: false
  }.merge(options)

  stimulus_data = {
    controller: 'gridjs',
    gridjs_autoload_value: true,
    gridjs_config_value: config.to_json
  }

  stimulus_data[:gridjs_data_value] = data.to_json if data
  stimulus_data[:gridjs_columns_value] = columns.to_json if columns

  (:div, data: stimulus_data, class: 'gridjs-wrapper') do
    (:div, '', data: { gridjs_target: 'table' }, id: id)
  end
end

#is_wy_ipObject



109
110
111
# File 'app/helpers/application_helper.rb', line 109

def is_wy_ip
  warmlyyours_ip?
end

#line_break(string, compact = false) ⇒ Object



179
180
181
182
183
# File 'app/helpers/application_helper.rb', line 179

def line_break(string, compact = false)
  res = string.to_s.strip.split(/[\n\r]/)
  res = res.select { |res| res.to_s.strip.present? } if compact
  res.join('<br>').html_safe
end

#parent_layout(layout) ⇒ Object



232
233
234
235
236
237
# File 'app/helpers/application_helper.rb', line 232

def parent_layout(layout)
  @view_flow.set(:layout, output_buffer)
  # this allows us to deal with random binary or non UTF-8 encoded input from the wild wild web and prevent mixing encodings which ruby/rails errors out on
  output = render(template: "layouts/#{layout}").force_encoding('UTF-8')
  self.output_buffer = ActionView::OutputBuffer.new(output)
end

#pass_or_fail(result) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'app/helpers/application_helper.rb', line 162

def pass_or_fail(result)
  return nil if result.nil?

  case result
  when 'pass'
    color = 'green'
    icon = 'check-circle'
  when 'fail'
    color = 'red'
    icon = 'times-circle'
  when 'unavailable', 'unchecked'
    color = 'orange'
    icon = 'exclamation-circle'
  end
  (:span, result.titleize) + ' ' + fa_icon(icon, style: "color:#{color};font-size:1.2em")
end

#render_error_messages_list(object) ⇒ Object



194
195
196
197
198
# File 'app/helpers/application_helper.rb', line 194

def render_error_messages_list(object)
  (:ul, class: 'list-group') do
    object.errors.full_messages.map { |msg| (:li, msg, class: 'list-group-item') }.join('').html_safe
  end
end

#render_video_card(video, layout: 'card', card_style: 'default', hide_title: false, hide_description: false, display_category_badge: false, display_duration: false, show_popup: true, show_direct_link: true, styles: '') ⇒ Object

Helper method to render video cards with consistent options



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'app/helpers/application_helper.rb', line 31

def render_video_card(video, layout: 'card', card_style: 'default', hide_title: false, hide_description: false, display_category_badge: false, display_duration: false, show_popup: true, show_direct_link: true, styles: '')
  return unless video

  # If video is already a presenter (from video_by_slug), use it directly
  # Otherwise, present it with Www::VideoPresenter
  video_presenter = video.is_a?(Www::VideoPresenter) ? video : present(video, Www::VideoPresenter)

  render Www::VideoCardComponent.new(
    video: video_presenter,
    layout: layout,
    card_style: card_style,
    hide_title: hide_title,
    hide_description: hide_description,
    display_category_badge: display_category_badge,
    display_duration: display_duration,
    show_popup: show_popup,
    show_direct_link: show_direct_link,
    styles: styles
  )
end

#resolved_auth_form_turbo_frame(turbo_frame: RESOLVED_AUTH_FRAME_UNSET) ⇒ Object



22
23
24
25
26
# File 'app/helpers/application_helper.rb', line 22

def resolved_auth_form_turbo_frame(turbo_frame: RESOLVED_AUTH_FRAME_UNSET)
  return turbo_frame unless turbo_frame.equal?(RESOLVED_AUTH_FRAME_UNSET)

  params[:turbo_target].to_s == 'navbar-account-frame' ? 'navbar-account-frame' : nil
end

#return_path_or(default) ⇒ Object

Returns @return_path (set by Controllers::ReturnPathHandling) when present,
otherwise falls back to the supplied default. Used in views for "Cancel"
links and similar nav-context links — does NOT perform a redirect.



203
204
205
# File 'app/helpers/application_helper.rb', line 203

def return_path_or(default)
  @return_path || default
end

#safe_css_color(color) ⇒ Object

Validates a CSS color value against safe patterns (hex codes and named colors).
Returns nil for anything that could contain injection payloads.



264
265
266
267
268
269
270
271
# File 'app/helpers/application_helper.rb', line 264

def safe_css_color(color)
  return nil if color.blank?

  stripped = color.strip
  return stripped if stripped.match?(/\A#[0-9a-fA-F]{3,8}\z/) || stripped.match?(/\A[a-zA-Z]{1,30}\z/)

  nil
end

#set_return_path_if_present(return_path: @return_path, return_title: nil) ⇒ Object



207
208
209
210
211
212
# File 'app/helpers/application_helper.rb', line 207

def set_return_path_if_present(return_path: @return_path, return_title: nil)
  capture do
    concat hidden_field_tag(:return_path, return_path) if return_path.present? && url_on_same_domain_as_request(return_path)
    concat hidden_field_tag(:return_title, return_title) if return_title.present?
  end
end

#set_section_if_presentObject



224
225
226
# File 'app/helpers/application_helper.rb', line 224

def set_section_if_present
  hidden_field_tag :section, params[:section] if params[:section].present?
end

#tab_frame_idObject

Per-controller frame ID so cross-page links inside tabs trigger turbo:frame-missing
instead of silently swapping the wrong page's tab shell.

When responding to a Turbo Frame request we sometimes echo the caller's frame ID
from the header so polymorphic controllers (Activities, Communications, ...) rendered
inside a parent's tab match the parent page's frame. We only do this when the request
is genuinely scoped to that parent -- i.e. the URL carries the parent's
<resource>_id route param (e.g. /customers/:customer_id/activities).

If a request originates from inside a parent's tab frame but targets a top-level
resource (e.g. clicking a contact card inside a customer's contacts tab and landing
on /contacts/:id), we deliberately fall through to the controller-derived id so
the response does NOT contain tab-content-<parent>. That makes Turbo fire
turbo:frame-missing, which our global handler in turbo_stream_actions.js upgrades
to a _top Drive visit using the already-fetched response.



292
293
294
295
296
297
298
299
300
301
302
303
# File 'app/helpers/application_helper.rb', line 292

def tab_frame_id
  default = "tab-content-#{controller_name}"
  header = request.headers['Turbo-Frame']
  return default unless header&.start_with?('tab-content-')
  return header if header == default

  parent_resource = header.delete_prefix('tab-content-')
  parent_id_param = "#{parent_resource.singularize}_id"
  return header if params[parent_id_param].present?

  default
end

#to_underscore(term) ⇒ Object



228
229
230
# File 'app/helpers/application_helper.rb', line 228

def to_underscore(term)
  term.tableize.singularize.tr(' ', '_')
end

#track_page?Boolean

Returns:

  • (Boolean)


113
114
115
116
117
# File 'app/helpers/application_helper.rb', line 113

def track_page?
  (Rails.env.production? || Rails.env.staging?) &&
    (@skip_analytics.nil? or @skip_analytics == false) &&
    !warmlyyours_ip?
end

#turbo_section_wrapper(id: nil, class_names: nil) ⇒ Object

Enable Turbo functionality for specific sections without affecting the entire page



252
253
254
255
256
257
258
259
260
# File 'app/helpers/application_helper.rb', line 252

def turbo_section_wrapper(id: nil, class_names: nil, &)
  turbo_attrs = {}
  turbo_attrs[:id] = id if id
  turbo_attrs[:class] = class_names if class_names

  turbo_attrs[:'data-turbo'] = 'true' if @turbo_frames_enabled

  (:div, turbo_attrs, &)
end

#turbo_tabs_request?Boolean

Returns:

  • (Boolean)


273
274
275
# File 'app/helpers/application_helper.rb', line 273

def turbo_tabs_request?
  request.headers['Turbo-Frame']&.start_with?('tab-content')
end

#url_on_same_domain_as_request(path) ⇒ Object



214
215
216
217
218
219
220
221
222
# File 'app/helpers/application_helper.rb', line 214

def url_on_same_domain_as_request(path)
  if path.present? and (uri = begin
    URI(path)
  rescue StandardError
    nil
  end)
    (uri.host.nil? or uri.host.index(request.host).present?)
  end
end

#widget_index_daily_focus_index_pathObject

AppSignal #2131: legacy references used widget_index_daily_focus_index_path; the route helper
from resources :daily_focus collection widget_index is widget_index_daily_focus_path.



307
308
309
# File 'app/helpers/application_helper.rb', line 307

def widget_index_daily_focus_index_path(...)
  widget_index_daily_focus_path(...)
end

#working_hours?Boolean

Returns:

  • (Boolean)


105
106
107
# File 'app/helpers/application_helper.rb', line 105

def working_hours?
  Time.current.in_working_hours?
end

#yes_or_no(value) ⇒ Object



140
141
142
# File 'app/helpers/application_helper.rb', line 140

def yes_or_no(value)
  value.to_b ? 'Yes' : 'No'
end

#yes_or_no_highlighted(b, reverse_check = false) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
# File 'app/helpers/application_helper.rb', line 150

def yes_or_no_highlighted(b, reverse_check = false)
  return nil if b.nil?

  res = yes_or_no(b)
  color = if reverse_check
            res == 'Yes' ? 'red' : 'green'
          else
            res == 'Yes' ? 'green' : 'red'
          end
  (:span, res, style: "color:#{color}")
end

#yes_or_no_with_check_or_cross(b, reverse_check = false) ⇒ Object



144
145
146
147
148
# File 'app/helpers/application_helper.rb', line 144

def yes_or_no_with_check_or_cross(b, reverse_check = false)
  return nil if b.nil?

  yes_or_no(b) + ' ' + check_or_cross(reverse_check ? !b : b)
end

#youtube_video(url) ⇒ Object



101
102
103
# File 'app/helpers/application_helper.rb', line 101

def youtube_video(url)
  render partial: 'shared/video', locals: { url: }
end