Module: ApplicationHelper

Overview

View helper: application.

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, #add_webpage_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



96
97
98
99
100
101
# File 'app/helpers/application_helper.rb', line 96

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



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

def check_force_logout
  return unless 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



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

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



138
139
140
# File 'app/helpers/application_helper.rb', line 138

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

#embedded_tab_frame_idObject

Frame id for views that are embeddable inside ANY tab regardless of caller.

Use for dual-purpose views (search_and_show, generic list pages) that render
full-screen when navigated directly but should slot into the calling tab when
fetched via Turbo-Frame: tab-content-*. Unlike tab_frame_id, this does
NOT gate on a parent_id route param — the embedded view doesn't know its
caller's resource.

Falls back to tab_frame_id (controller-derived default) when there's no
tab-content-* header, so direct navigation still works.



326
327
328
329
330
331
# File 'app/helpers/application_helper.rb', line 326

def embedded_tab_frame_id
  header = request.headers['Turbo-Frame']
  return header if header&.start_with?('tab-content-')

  tab_frame_id
end

#error_messages(object) ⇒ Object



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

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



241
242
243
# File 'app/helpers/application_helper.rb', line 241

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



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

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



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

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



111
112
113
# File 'app/helpers/application_helper.rb', line 111

def is_wy_ip
  warmlyyours_ip?
end

#line_break(string, compact = false) ⇒ Object



181
182
183
184
185
# File 'app/helpers/application_helper.rb', line 181

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



234
235
236
237
238
239
# File 'app/helpers/application_helper.rb', line 234

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



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

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



196
197
198
199
200
# File 'app/helpers/application_helper.rb', line 196

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



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

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



24
25
26
27
28
# File 'app/helpers/application_helper.rb', line 24

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.



205
206
207
# File 'app/helpers/application_helper.rb', line 205

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.



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

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



209
210
211
212
213
214
# File 'app/helpers/application_helper.rb', line 209

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



226
227
228
# File 'app/helpers/application_helper.rb', line 226

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

#tab_frame_idObject

Frame id for tab partial views.

Returns the controller-derived tab-content-<controller_name> by default.
Echoes the caller's Turbo-Frame: tab-content-<parent> header only when
the URL carries the parent's <resource>_id route param — i.e. the request
is genuinely scoped to that parent (e.g. /customers/:customer_id/activities).

Why the parent_id gate:

When a top-level resource (e.g. /orders/:id) is requested via a Turbo Frame
fetch from inside a parent's tab (Turbo-Frame: tab-content-warehouses), we
want the response to contain tab-content-orders (this controller's own
frame), NOT tab-content-warehouses. The frame mismatch fires
turbo:frame-missing, which the global handler in turbo_stream_actions.js
upgrades to a full Drive visit — the right UX for clicking "into" a different
resource.

Without the gate, the show page's outer tab_panel frame would be echoed to
match the caller, the response would slot into the parent's tab as an empty
lazy shell, and the empty-shell trap would Drive-visit the inner tab URL —
the user lands on /orders/:id/tab_line_items instead of /orders/:id.

Embed-anywhere views (search/list views meant to render under any tab) should
use embedded_tab_frame_id instead — that one always echoes.



303
304
305
306
307
308
309
310
311
312
313
314
# File 'app/helpers/application_helper.rb', line 303

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



230
231
232
# File 'app/helpers/application_helper.rb', line 230

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

#track_page?Boolean

Returns:

  • (Boolean)


115
116
117
118
119
# File 'app/helpers/application_helper.rb', line 115

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



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

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)


275
276
277
# File 'app/helpers/application_helper.rb', line 275

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

#url_on_same_domain_as_request(path) ⇒ Object



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

def url_on_same_domain_as_request(path)
  if path.present? && (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.



335
336
337
# File 'app/helpers/application_helper.rb', line 335

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

#working_hours?Boolean

Returns:

  • (Boolean)


107
108
109
# File 'app/helpers/application_helper.rb', line 107

def working_hours?
  Time.current.in_working_hours?
end

#yes_or_no(value) ⇒ Object



142
143
144
# File 'app/helpers/application_helper.rb', line 142

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

#yes_or_no_highlighted(b, reverse_check = false) ⇒ Object



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

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



146
147
148
149
150
# File 'app/helpers/application_helper.rb', line 146

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



103
104
105
# File 'app/helpers/application_helper.rb', line 103

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