Module: Spree::FrontendHelper

Includes:
BaseHelper, InlineSvg::ActionView::Helpers
Included in:
CmsPagesController, ProductsController, TaxonsController
Defined in:
app/helpers/spree/frontend_helper.rb

Instance Method Summary collapse

Instance Method Details

#additional_filters_partialsObject



272
273
274
# File 'app/helpers/spree/frontend_helper.rb', line 272

def additional_filters_partials
  @additional_filters_partials ||= Spree::Frontend::Config[:additional_filters_partials]
end

#asset_exists?(path) ⇒ Boolean

Returns:

  • (Boolean)


162
163
164
165
166
167
168
# File 'app/helpers/spree/frontend_helper.rb', line 162

def asset_exists?(path)
  if Rails.configuration.assets.compile
    Rails.application.precompiled_assets.include? path
  else
    Rails.application.assets_manifest.assets[path].present?
  end
end

#available_option_typesObject



337
338
339
340
341
342
# File 'app/helpers/spree/frontend_helper.rb', line 337

def available_option_types
  @available_option_types ||= Rails.cache.fetch("available-option-types/#{available_option_types_cache_key}") do
    option_values = OptionValues::FindAvailable.new(products_scope: products_for_filters).execute
    Filters::OptionsPresenter.new(option_values_scope: option_values).to_a
  end
end

#available_option_types_cache_keyObject



330
331
332
333
334
335
# File 'app/helpers/spree/frontend_helper.rb', line 330

def available_option_types_cache_key
  @available_option_types_cache_key ||= [
    Spree::OptionType.filterable.maximum(:updated_at).to_f,
    products_for_filters_cache_key
  ].flatten.join('/')
end

#available_propertiesObject



351
352
353
354
355
356
# File 'app/helpers/spree/frontend_helper.rb', line 351

def available_properties
  @available_properties ||= Rails.cache.fetch("available-properties/#{available_properties_cache_key}") do
    product_properties = ProductProperties::FindAvailable.new(products_scope: products_for_filters).execute
    Filters::PropertiesPresenter.new(product_properties_scope: product_properties).to_a
  end
end

#available_properties_cache_keyObject



344
345
346
347
348
349
# File 'app/helpers/spree/frontend_helper.rb', line 344

def available_properties_cache_key
  @available_properties_cache_key ||= [
    Spree::Property.filterable.maximum(:updated_at).to_f,
    products_for_filters_cache_key
  ].flatten.join('/')
end

#body_classObject



6
7
8
9
# File 'app/helpers/spree/frontend_helper.rb', line 6

def body_class
  @body_class ||= content_for?(:sidebar) ? 'two-col' : 'one-col'
  @body_class
end


203
204
205
206
207
208
209
210
211
212
213
214
# File 'app/helpers/spree/frontend_helper.rb', line 203

def carousel_image_source_set(image)
  return '' unless image

  widths = { lg: 1200, md: 992, sm: 768, xs: 576 }
  set = []
  widths.each do |key, value|
    file = main_app.cdn_image_url(image.url("plp_and_carousel_#{key}"))

    set << "#{file} #{value}w"
  end
  set.join(', ')
end

#checkout_available_payment_methodsObject



368
369
370
# File 'app/helpers/spree/frontend_helper.rb', line 368

def checkout_available_payment_methods
  @checkout_available_payment_methods ||= @order.available_payment_methods
end

#checkout_progress(numbers: false) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'app/helpers/spree/frontend_helper.rb', line 91

def checkout_progress(numbers: false)
  states = @order.checkout_steps - ['complete']
  items = states.each_with_index.map do |state, i|
    text = Spree.t("order_state.#{state}").titleize
    text.prepend("#{i.succ}. ") if numbers

    css_classes = ['text-uppercase nav-item']
    current_index = states.index(@order.state)
    state_index = states.index(state)

    if state_index < current_index
      css_classes << 'completed'
      link_content =  :span, nil, class: 'checkout-progress-steps-image checkout-progress-steps-image--full'
      link_content << text
      text = link_to(link_content, spree.checkout_state_path(state), class: 'd-flex flex-column align-items-center', method: :get)
    end

    css_classes << 'next' if state_index == current_index + 1
    css_classes << 'active' if state == @order.state
    css_classes << 'first' if state_index == 0
    css_classes << 'last' if state_index == states.length - 1
    # No more joined classes. IE6 is not a target browser.
    # Hack: Stops <a> being wrapped round previous items twice.
    if state_index < current_index
      ('li', text, class: css_classes.join(' '))
    else
      link_content = if state == @order.state
                        :span, nil, class: 'checkout-progress-steps-image checkout-progress-steps-image--full'
                     else
                       inline_svg_tag 'circle.svg', class: 'checkout-progress-steps-image'
                     end
      link_content << text
      ('li', ('a', link_content, class: "d-flex flex-column align-items-center #{'active' if state == @order.state}"), class: css_classes.join(' '))
    end
  end
  content = ('ul', raw(items.join("\n")), class: 'nav justify-content-between checkout-progress-steps', id: "checkout-step-#{@order.state}")
  hrs = '<hr />' * (states.length - 1)
  content << ('div', raw(hrs), class: "checkout-progress-steps-line state-#{@order.state}")
end

#class_for(flash_type) ⇒ Object



80
81
82
83
84
85
86
87
88
89
# File 'app/helpers/spree/frontend_helper.rb', line 80

def class_for(flash_type)
  {
    success: 'success',
    registration_error: 'danger',
    error: 'danger',
    alert: 'danger',
    warning: 'warning',
    notice: 'success'
  }[flash_type.to_sym]
end

#color_option_type_nameObject



372
373
374
# File 'app/helpers/spree/frontend_helper.rb', line 372

def color_option_type_name
  @color_option_type_name ||= Spree::OptionType.color&.name
end

#country_flag_icon(country_iso_code = nil) ⇒ Object



376
377
378
379
380
# File 'app/helpers/spree/frontend_helper.rb', line 376

def country_flag_icon(country_iso_code = nil)
  return if country_iso_code.blank?

   :span, nil, class: "flag-icon flag-icon-#{country_iso_code.downcase}"
end

#filtering_paramsObject



276
277
278
# File 'app/helpers/spree/frontend_helper.rb', line 276

def filtering_params
  @filtering_params ||= available_option_types.map(&:filter_param).concat(static_filters)
end

#filtering_params_cache_keyObject



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# File 'app/helpers/spree/frontend_helper.rb', line 280

def filtering_params_cache_key
  @filtering_params_cache_key ||= begin
    cache_key_parts = []

    permitted_products_params.each do |key, value|
      next if value.blank?

      if value.is_a?(String)
        cache_key_parts << [key, value].join('-')
      else
        value.each do |part_key, part_value|
          next if part_value.blank?

          cache_key_parts << [part_key, part_value].join('-')
        end
      end
    end

    cache_key_parts.join('-').parameterize
  end
end

#filters_cache_key(kind) ⇒ Object



302
303
304
305
306
307
308
309
310
311
# File 'app/helpers/spree/frontend_helper.rb', line 302

def filters_cache_key(kind)
  base_cache_key + [
    kind,
    available_option_types_cache_key,
    available_properties_cache_key,
    filtering_params_cache_key,
    @taxon&.id,
    params[:menu_open]
  ].flatten
end

#flash_messages(opts = {}) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'app/helpers/spree/frontend_helper.rb', line 131

def flash_messages(opts = {})
  flashes = ''
  excluded_types = opts[:excluded_types].to_a.map(&:to_s)

  flash.to_h.except('order_completed').each do |msg_type, text|
    next if msg_type.blank? || excluded_types.include?(msg_type)

    flashes << (:div, class: "alert alert-#{class_for(msg_type)} mb-0") do
      (:button, '&times;'.html_safe, class: 'close', data: { dismiss: 'alert', hidden: true }) +
        (:span, text)
    end
  end
  flashes.html_safe
end

#icon(name:, classes: '', width:, height:) ⇒ Object



249
250
251
# File 'app/helpers/spree/frontend_helper.rb', line 249

def icon(name:, classes: '', width:, height:)
  inline_svg_tag "#{name}.svg", class: "spree-icon #{classes}", size: "#{width}px*#{height}px"
end

#image_source_set(name) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'app/helpers/spree/frontend_helper.rb', line 216

def image_source_set(name)
  widths = {
    desktop: '1200',
    tablet_landscape: '992',
    tablet_portrait: '768',
    mobile: '576'
  }
  set = []
  widths.each do |key, value|
    filename = key == :desktop ? name : "#{name}_#{key}"
    file = asset_path("#{filename}.jpg")

    set << "#{file} #{value}w"
  end
  set.join(', ')
end

#lazy_image(src:, alt:, width:, height:, srcset: '', **options) ⇒ Object



191
192
193
194
195
196
# File 'app/helpers/spree/frontend_helper.rb', line 191

def lazy_image(src:, alt:, width:, height:, srcset: '', **options)
  # We need placeholder image with the correct size to prevent page from jumping
  placeholder = "data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20#{width}%20#{height}'%3E%3C/svg%3E"

  image_tag placeholder, data: { src: src, srcset: srcset }, class: "#{options[:class]} lazyload", alt: alt
end


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'app/helpers/spree/frontend_helper.rb', line 146

def link_to_cart(text = nil)
  text = text ? h(text) : Spree.t('cart')
  css_class = nil

  if simple_current_order.nil? || simple_current_order.item_count.zero?
    text = "<span class='glyphicon glyphicon-shopping-cart'></span> #{text}: (#{Spree.t('empty')})"
    css_class = 'empty'
  else
    text = "<span class='glyphicon glyphicon-shopping-cart'></span> #{text}: (#{simple_current_order.item_count})
            <span class='amount'>#{simple_current_order.display_total.to_html}</span>"
    css_class = 'full'
  end

  link_to text.html_safe, spree.cart_path, class: "cart-info nav-link #{css_class}"
end

#logo(image_path = nil, options = {}) ⇒ Object



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'app/helpers/spree/frontend_helper.rb', line 11

def (image_path = nil, options = {})
  logo_attachment = if defined?(Spree::StoreLogo) && current_store..is_a?(Spree::StoreLogo)
                      current_store..attachment # Spree v5
                    else
                      current_store. # Spree 4.x
                    end

  image_path ||= if logo_attachment&.attached? && logo_attachment&.variable?
                   main_app.cdn_image_url(logo_attachment.variant(resize: '244x104>'))
                 elsif logo_attachment&.attached? && logo_attachment&.image?
                   main_app.cdn_image_url(logo_attachment)
                 else
                   asset_path('logo/spree_50.png')
                 end

  path = spree.respond_to?(:root_path) ? spree.root_path : main_app.root_path

  link_to path, 'aria-label': current_store.name, method: options[:method] do
    image_tag image_path, alt: current_store.name, title: current_store.name
  end
end

#option_type_cache_key(option_type) ⇒ Object



319
320
321
322
323
324
325
326
327
328
# File 'app/helpers/spree/frontend_helper.rb', line 319

def option_type_cache_key(option_type)
  filter_param = option_type.filter_param
  filtered_params = params[filter_param]

  [
    available_option_types_cache_key,
    filter_param,
    filtered_params
  ]
end

#permitted_product_paramsObject



198
199
200
201
# File 'app/helpers/spree/frontend_helper.rb', line 198

def permitted_product_params
  product_filters = available_option_types.map(&:name)
  params.permit(product_filters << :sort_by)
end

#permitted_products_paramsObject



313
314
315
316
317
# File 'app/helpers/spree/frontend_helper.rb', line 313

def permitted_products_params
  @permitted_products_params ||= begin
    params.permit(*filtering_params, properties: available_properties.map(&:filter_param))
  end
end


170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'app/helpers/spree/frontend_helper.rb', line 170

def plp_and_carousel_image(product, image_class = '')
  image = default_image_for_product_or_variant(product)

  image_url = if image.present?
                main_app.cdn_image_url(image.url('plp'))
              else
                asset_path('noimage/large.png')
              end

  image_style = image&.style(:plp)

  lazy_image(
    src: image_url,
    srcset: carousel_image_source_set(image),
    alt: product.name,
    width: image_style&.dig(:width) || 278,
    height: image_style&.dig(:height) || 371,
    class: "product-component-image d-block mw-100 #{image_class}"
  )
end

#price_filter_valuesObject



253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'app/helpers/spree/frontend_helper.rb', line 253

def price_filter_values
  Spree::Deprecation.warn(<<-DEPRECATION, caller)
    `FrontendHelper#price_filter_values` is deprecated and will be removed in Spree 5.0.
    Please use `ProductsFiltersHelper#price_filters` method
  DEPRECATION

  @price_filter_values ||= [
    "#{I18n.t('activerecord.attributes.spree/product.less_than')} #{formatted_price(50)}",
    "#{formatted_price(50)} - #{formatted_price(100)}",
    "#{formatted_price(101)} - #{formatted_price(150)}",
    "#{formatted_price(151)} - #{formatted_price(200)}",
    "#{formatted_price(201)} - #{formatted_price(300)}"
  ]
end

#product_description(product) ⇒ Object

converts line breaks in product description into <p> tags (for html display purposes)



391
392
393
394
395
396
397
398
# File 'app/helpers/spree/frontend_helper.rb', line 391

def product_description(product)
  description = if Spree::Frontend::Config[:show_raw_product_description] || product_wysiwyg_editor_enabled?
                  product.description
                else
                  product.description.to_s.gsub(/(.*?)\r?\n\r?\n/m, '<p>\1</p>')
                end
  description.blank? ? Spree.t(:product_has_no_description) : description
end

#product_wysiwyg_editor_enabled?Boolean

Returns:

  • (Boolean)


382
383
384
# File 'app/helpers/spree/frontend_helper.rb', line 382

def product_wysiwyg_editor_enabled?
  defined?(Spree::Backend) && Spree::Backend::Config[:product_wysiwyg_editor_enabled]
end

#set_image_alt(image) ⇒ Object



245
246
247
# File 'app/helpers/spree/frontend_helper.rb', line 245

def set_image_alt(image)
  return image.alt if image.alt.present?
end

#spree_breadcrumbs(taxon, _separator = '', product = nil) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'app/helpers/spree/frontend_helper.rb', line 33

def spree_breadcrumbs(taxon, _separator = '', product = nil)
  return '' if current_page?('/') || taxon.nil?

  # breadcrumbs for root
  crumbs = [(:li, (
    :a, (
      :span, Spree.t(:home), itemprop: 'name'
    ) << (:meta, nil, itemprop: 'position', content: '0'), itemprop: 'url', href: spree.root_path
  ) << (:span, nil, itemprop: 'item', itemscope: 'itemscope', itemtype: 'https://schema.org/Thing', itemid: spree.root_path), itemscope: 'itemscope', itemtype: 'https://schema.org/ListItem', itemprop: 'itemListElement', class: 'breadcrumb-item')]

  if taxon
    ancestors = taxon.ancestors.where.not(parent_id: nil)

    # breadcrumbs for ancestor taxons
    crumbs << ancestors.each_with_index.map do |ancestor, index|
      (:li, (
        :a, (
          :span, ancestor.name, itemprop: 'name'
        ) << (:meta, nil, itemprop: 'position', content: index + 1), itemprop: 'url', href: seo_url(ancestor, params: permitted_product_params)
      ) << (:span, nil, itemprop: 'item', itemscope: 'itemscope', itemtype: 'https://schema.org/Thing', itemid: seo_url(ancestor, params: permitted_product_params)), itemscope: 'itemscope', itemtype: 'https://schema.org/ListItem', itemprop: 'itemListElement', class: 'breadcrumb-item')
    end

    # breadcrumbs for current taxon
    crumbs << (:li, (
      :a, (
        :span, taxon.name, itemprop: 'name'
      ) << (:meta, nil, itemprop: 'position', content: ancestors.size + 1), itemprop: 'url', href: seo_url(taxon, params: permitted_product_params)
    ) << (:span, nil, itemprop: 'item', itemscope: 'itemscope', itemtype: 'https://schema.org/Thing', itemid: seo_url(taxon, params: permitted_product_params)), itemscope: 'itemscope', itemtype: 'https://schema.org/ListItem', itemprop: 'itemListElement', class: 'breadcrumb-item')

    # breadcrumbs for product
    if product
      crumbs << (:li, (
        :span, (
          :span, product.name, itemprop: 'name'
        ) << (:meta, nil, itemprop: 'position', content: ancestors.size + 2), itemprop: 'url', href: spree.product_path(product, taxon_id: taxon&.id)
      ) << (:span, nil, itemprop: 'item', itemscope: 'itemscope', itemtype: 'https://schema.org/Thing', itemid: spree.product_path(product, taxon_id: taxon&.id)), itemscope: 'itemscope', itemtype: 'https://schema.org/ListItem', itemprop: 'itemListElement', class: 'breadcrumb-item')
    end
  else
    # breadcrumbs for product on PDP
    crumbs << (:li, (
      :span, Spree.t(:products), itemprop: 'item'
    ) << (:meta, nil, itemprop: 'position', content: '1'), class: 'active', itemscope: 'itemscope', itemtype: 'https://schema.org/ListItem', itemprop: 'itemListElement')
  end
  crumb_list = (:ol, raw(crumbs.flatten.map(&:mb_chars).join), class: 'breadcrumb', itemscope: 'itemscope', itemtype: 'https://schema.org/BreadcrumbList')
  (:nav, crumb_list, id: 'breadcrumbs', class: 'col-12 mt-1 mt-sm-3 mt-lg-4', aria: { label: Spree.t(:breadcrumbs) })
end


358
359
360
361
362
363
364
365
366
# File 'app/helpers/spree/frontend_helper.rb', line 358

def spree_social_link(service)
  return '' if current_store.send(service).blank?

  link_to "https://#{service}.com/#{current_store.send(service)}", target: '_blank', rel: 'nofollow noopener', 'aria-label': service do
     :figure, id: service, class: 'px-2' do
      icon(name: service, width: 22, height: 22)
    end
  end
end

#static_filtersObject



268
269
270
# File 'app/helpers/spree/frontend_helper.rb', line 268

def static_filters
  @static_filters ||= Spree::Frontend::Config[:products_filters]
end

#taxon_wysiwyg_editor_enabled?Boolean

Returns:

  • (Boolean)


386
387
388
# File 'app/helpers/spree/frontend_helper.rb', line 386

def taxon_wysiwyg_editor_enabled?
  defined?(Spree::Backend) && Spree::Backend::Config[:taxon_wysiwyg_editor_enabled]
end

#taxons_tree(root_taxon, current_taxon, max_level = 1) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
# File 'app/helpers/spree/frontend_helper.rb', line 233

def taxons_tree(root_taxon, current_taxon, max_level = 1)
  return '' if max_level < 1 || root_taxon.leaf?

   :div, class: 'list-group' do
    taxons = root_taxon.children.map do |taxon|
      css_class = current_taxon&.self_and_ancestors&.include?(taxon) ? 'list-group-item list-group-item-action active' : 'list-group-item list-group-item-action'
      link_to(taxon.name, seo_url(taxon), class: css_class) + taxons_tree(taxon, current_taxon, max_level - 1)
    end
    safe_join(taxons, "\n")
  end
end