Class: Www::ProductCompareTableComponent
- Inherits:
-
ApplicationComponent
- Object
- ViewComponent::Base
- ApplicationComponent
- Www::ProductCompareTableComponent
- Defined in:
- app/components/www/product_compare_table_component.rb
Overview
Www::ProductCompareTableComponent
Renders a responsive product comparison table with products displayed in columns
and features listed on the left side. Supports mobile swiping and responsive
column display from 1 column on mobile to 5 columns on largest viewports.
Constructor parameters (keyword args)
- skus: Array of SKUs to compare
- features: Array of feature keys to display (optional, will auto-detect if not provided)
- section_title: Optional title for the comparison section
- section_description: Optional description for the comparison section
Features are automatically detected from the first product if not provided.
Each feature is displayed as a row with checkmarks or X marks for each product.
Constant Summary collapse
- SKU_FEATURES =
Feature definitions per SKU - much easier to maintain than nested case statements
{ # Sensors are never included with standalone controls — they are sold separately. # Use :aerial / :slab symbols so the template renders a product link instead of a checkmark. 'SC-MZ-TOUCH' => { 'automatic' => true, 'moisture_sensor' => :aerial, 'air_temp_sensor' => :aerial, 'slab_temp_sensor' => :slab, 'digital_temp_readout' => true, 'adjust_after_run_time' => true, 'multi_zone_capability' => true, 'max_load' => 'N/A max load' }, 'SCP-120' => { 'automatic' => true, 'outdoor_rated' => true, 'moisture_sensor' => :aerial, 'air_temp_sensor' => :aerial, 'slab_temp_sensor' => :slab, 'adjust_after_run_time' => true, 'max_load' => '24 amps max load' }, 'SCA-DUAL' => { 'automatic' => true, 'outdoor_rated' => true, 'moisture_sensor' => :aerial, 'air_temp_sensor' => :aerial, 'slab_temp_sensor' => :slab, # supports "pavement" sensor per product features 'adjust_after_run_time' => true, 'max_load' => '30 amps max load' }, 'SCE-120' => { 'automatic' => true, 'moisture_sensor' => :aerial, 'air_temp_sensor' => :aerial, 'slab_temp_sensor' => :slab, 'digital_temp_readout' => true, 'adjust_after_run_time' => true, 'max_load' => '3 x 16 amps max load' }, 'SCV-DUAL' => { 'automatic' => true, 'outdoor_rated' => true, 'moisture_sensor' => true, # built-in snow & temperature sensor 'air_temp_sensor' => true, # built-in snow & temperature sensor 'adjust_after_run_time' => true, 'max_load' => '16 amps max load' }, 'SCM-DUAL' => { 'timer' => true, 'max_load' => '20 Amp, 120 VAC, 50/60 Hz; 10 Amp, 240 VAC, 50/60 Hz; 10 Amp, 277 VAC, 50/60 Hz amps max load' }, 'SCW-120-15A' => { 'control_remotely_via_iphone_android_or_web_app' => true, 'max_load' => '15A @ 120V amps max load' }, # ── Panel & Towel Warmer Controls ─────────────────────────────────────── 'GK16-30090-0006' => { 'countdown_timer' => true, # 4 preset durations: 4 hr, 2 hr, 1 hr, 30 min 'programmable' => false, 'schedules' => false, 'wifi' => false, 'voice_control' => false, 'remote_support' => false, 'display' => 'None', 'max_load' => '15A @ 120V' }, 'GK16-30090-0002' => { 'countdown_timer' => false, 'programmable' => true, # astronomical clock + up to 50 on/off events 'schedules' => 'Up to 50 on/off events', 'wifi' => false, 'voice_control' => false, 'remote_support' => false, 'display' => 'Backlit LCD', 'max_load' => '15A @ 120V' }, 'GK16-30090-0003' => { 'countdown_timer' => false, 'programmable' => true, # schedule via My Leviton app 'schedules' => 'Via My Leviton app', 'wifi' => true, 'voice_control' => true, # Alexa, Google, Apple HomeKit/Siri, SmartThings, IFTTT 'remote_support' => false, 'display' => 'None', 'max_load' => '15A @ 120V' }, # ── Floor Heating Thermostats ──────────────────────────────────────────── 'UWG5-4999-WY' => { 'countdown_timer' => false, 'wifi' => true, 'remote_support' => true, 'voice_control' => true, 'programmable' => true, 'schedules' => 'Up to 10 schedules', 'floor_sensor_included' => true, 'display' => 'LED touch', 'max_load' => '15A @ 120/240V' }, 'UTN5-4999' => { 'countdown_timer' => false, 'wifi' => false, 'remote_support' => false, 'voice_control' => false, 'programmable' => false, 'schedules' => 'Set-and-hold', 'floor_sensor_included' => true, 'display' => 'LED touch', 'max_load' => '15A @ 120/240V' }, 'UDG4-4999' => { 'countdown_timer' => false, 'wifi' => false, 'remote_support' => false, 'voice_control' => false, 'programmable' => true, 'schedules' => 'Up to 6 per day', 'floor_sensor_included' => true, 'display' => '3.5" color touchscreen', 'max_load' => '15A @ 120/240V' }, # Canadian variant of nSpire Touch — same features as UDG4-4999 'UDG4-4999-WY' => { 'countdown_timer' => false, 'wifi' => false, 'remote_support' => false, 'voice_control' => false, 'programmable' => true, 'schedules' => 'Up to 6 per day', 'floor_sensor_included' => true, 'display' => '3.5" color touchscreen', 'max_load' => '15A @ 120/240V' }, # nJoin Power Module — extends thermostat capacity for high-load zones. # Not a standalone thermostat: schedules and sensor come from the paired thermostat. 'USG5-4000' => { 'countdown_timer' => false, 'wifi' => false, 'remote_support' => false, 'voice_control' => false, 'programmable' => false, 'schedules' => 'Via paired thermostat', 'floor_sensor_included' => false, 'display' => 'None', 'max_load' => '15A @ 120/208/240V' }, # Canadian variant — identical specs to USG5-4000 'USG-4000' => { 'countdown_timer' => false, 'wifi' => false, 'remote_support' => false, 'voice_control' => false, 'programmable' => false, 'schedules' => 'Via paired thermostat', 'floor_sensor_included' => false, 'display' => 'None', 'max_load' => '15A @ 120/208/240V' }, # Pipe Freeze Protection Controls (also used in roof and gutter deicing) 'AIR-STAT' => { # Snow melting features 'automatic' => true, 'outdoor_rated' => true, 'moisture_sensor' => false, 'air_temp_sensor' => true, 'slab_temp_sensor' => false, 'digital_temp_readout' => false, 'adjust_after_run_time' => false, 'timer' => false, 'control_remotely_via_iphone_android_or_web_app' => false, # Pipe freeze protection features 'included_sensor' => '1 capillary bulb, 3\' lead', 'temperature_setpoint' => '40° F', 'indicators' => 'None', 'built_in_gfi' => 'None', 'waterproofing' => 'NEMA 4X', 'area_of_use' => 'Non-hazardous locations', 'max_load' => '22 amps max load' }, 'PT-ECONOMY' => { 'included_sensor' => '1 Sensor (20\' lead)', 'temperature_setpoint' => '30 °F, 38 °F, 45 °F, or 50 °F', 'indicators' => 'LED Indicators', 'built_in_gfi' => 'Self-test GFEP 30mA', 'waterproofing' => 'NEMA 4X', 'area_of_use' => 'Non-hazardous locations', 'max_load' => '30 amps max load' }, 'PT-SINGLE' => { 'included_sensor' => '100k ohms, 20\' lead', 'temperature_setpoint' => 'Adjustable -99.9 °F to 999 °F', 'indicators' => '2.7" Display', 'built_in_gfi' => 'Self-test GFEP (Adjustable 1-300mA)', 'waterproofing' => 'NEMA 4X', 'area_of_use' => 'Non-hazardous locations', 'max_load' => '30 amps max load' }, 'PT-DUAL' => { 'included_sensor' => '2 x 100k ohms, 20\' lead', 'temperature_setpoint' => 'Adjustable -99.9 °F to 999 °F', 'indicators' => '2.7" Display', 'built_in_gfi' => 'Self-test GFEP (Adjustable 1-300mA)', 'waterproofing' => 'NEMA 4X', 'area_of_use' => 'Non-hazardous locations', 'max_load' => '2 x 30 amps max load' }, # ── Snow Melting Sensors ───────────────────────────────────────────────── 'AIR-SS-2' => { 'sensor_installation' => 'Mounted above ground on post or wall', 'detects_precipitation' => true, 'measures_air_temp' => true, 'measures_slab_temp' => false, 'retrofit_compatible' => true, 'compatible_controls' => 'SC-MZ-TOUCH, SCP-120, SCA-DUAL, SCE-120' }, 'SCE-SLAB-SS-KIT' => { 'sensor_installation' => 'Embedded in concrete during pour', 'detects_precipitation' => false, 'measures_air_temp' => false, 'measures_slab_temp' => true, 'retrofit_compatible' => false, 'compatible_controls' => 'SC-MZ-TOUCH, SCP-120, SCA-DUAL, SCE-120' } }.freeze
- ADD_ON_PRODUCTS =
Products that can be added to extend a control's capabilities.
The symbol keys are used as feature values in SKU_FEATURES to signal
"this feature is supported but requires a separate purchase".
anchor: scrolls to the sensors comparison section on the same page. { aerial: { sku: 'AIR-SS-2', label: 'Aerial Sensor', anchor: '#sensors' }, slab: { sku: 'SCE-SLAB-SS-KIT', label: 'Slab Sensor Kit', anchor: '#sensors' } }.freeze
- SENSOR_FEATURE_KEYS =
Feature keys that represent physical sensors — these get badge treatment
("Included" or "Pair with…") instead of a plain checkmark/X. %w[moisture_sensor air_temp_sensor slab_temp_sensor].freeze
- BEST_FOR =
"Best for" taglines shown in the comparison table header, below the product title.
Authored specifically for the comparison context — more decision-oriented than
generic DB short_description copy. Takes precedence over item.short_description. { # ── Floor Heating Thermostats ────────────────────────────────────────────── 'UTN5-4999' => 'Simple set-and-hold control', 'UDG4-4999' => 'Touchscreen, no WiFi needed', 'UDG4-4999-WY' => 'Touchscreen, no WiFi needed', 'UWG5-4999-WY' => 'WiFi + Alexa / Google / HomeKit', # ── Radiant Panel & Towel Warmer Controls ────────────────────────────────── 'GK16-30090-0006' => 'Simple timer, no programming', 'GK16-30090-0002' => 'Scheduled daily heating', 'GK16-30090-0003' => 'Smart home & voice control', # ── Snow Melting Controls ────────────────────────────────────────────────── 'SC-MZ-TOUCH' => 'Multi-zone touchscreen hub', 'SCP-120' => 'Basic single-zone manual', 'SCA-DUAL' => 'Automatic with external sensor', 'SCE-120' => 'Economical automatic control', 'SCV-DUAL' => 'Automatic, no sensor needed', 'SCM-DUAL' => 'Manual timer control', 'SCW-120-15A' => 'WiFi remote control', 'SMRT-IQ' => 'WiFi remote monitoring', # ── Pipe Freeze & Roof/Gutter Controls ──────────────────────────────────── 'AIR-STAT' => 'Temperature-only freeze protection', 'RXSJS2-Z1SP' => 'Automatic freeze protection'}.freeze
- FEATURE_TOOLTIPS =
{ 'automatic' => 'Uses sensors to activate the system automatically when snow or ice is detected—no manual intervention needed.', 'outdoor_rated' => 'The control enclosure (and sensors) are rated to withstand harsh outdoor environments including moisture, frost, and temperature extremes.', 'moisture_sensor' => 'Detects the presence of snow, rain, or moisture above the heated surface. The system only activates when both moisture and a below-freezing air temperature are detected—preventing unnecessary heating.', 'air_temp_sensor' => 'Monitors outdoor air temperature. Combined with moisture detection, it ensures the system only runs when conditions actually require it, saving energy.', 'slab_temp_sensor' => 'Measures the surface temperature of the concrete or pavers directly. The system heats until the slab reaches the target temperature, then shuts off automatically.', 'digital_temp_readout' => 'Shows the current sensor temperature on the control\'s display so you can monitor conditions without a separate thermometer.', 'adjust_after_run_time' => 'Keeps the system running for a set time after precipitation stops to ensure the surface dries fully and prevent re-freezing.', 'multi_zone_capability' => 'Controls multiple independent heating zones from a single unit—useful for driveways, walkways, and steps on the same installation.', 'timer' => 'A built-in 24-hour timer lets you schedule the system to run during specific hours. No sensors required—ideal for simple on/off control.', 'countdown_timer' => 'Preset countdown durations let you run the system for a fixed period (e.g. 30 min, 1 hr, 2 hr, 4 hr) then shut off automatically—no programming required.', 'control_remotely_via_iphone_android_or_web_app' => 'Connects via WiFi so you can monitor and control the system from anywhere using a smartphone app or web browser. Compatible with Alexa, Google Home, Nest, and IFTTT.', 'max_load' => 'The maximum load this control can switch directly.', 'wifi' => 'Built-in WiFi lets you monitor and adjust settings remotely from a smartphone app.', 'remote_support' => 'Allows WarmlyYours technicians to diagnose issues and push firmware updates over the internet.', 'voice_control' => 'Compatible with Amazon Alexa, Google Assistant, and Apple HomeKit/Siri for hands-free voice commands and home automation routines.', 'programmable' => 'Set daily on/off schedules so the system runs exactly when you need it without manual intervention.', 'schedules' => 'Number of independent on/off schedule events available for programming.', 'floor_sensor_included' => 'Includes a floor temperature sensor so the thermostat can regulate the actual floor temperature, not just the air.', 'included_sensor' => 'The temperature sensor shipped in the box with this control.', 'temperature_setpoint' => 'The temperature range you can set for the control to maintain.', 'built_in_gfi' => 'Ground Fault Equipment Protection trips the circuit if a fault is detected, protecting both the equipment and personnel.', 'waterproofing' => 'The NEMA enclosure rating indicates how well the control resists water, dust, and corrosion.', 'indicators' => 'Status lights or display that show the current operating state of the control.', 'area_of_use' => 'The environments where this control is certified for installation (e.g. non-hazardous indoor locations).', # Sensor-specific 'sensor_installation' => 'Where and how this sensor is physically installed.', 'detects_precipitation' => 'Senses snow, rain, or ice above the surface. Used with air temperature to decide when the system should activate.', 'measures_air_temp' => 'Reads outdoor air temperature. Prevents the system from running when it\'s too warm to need heating.', 'measures_slab_temp' => 'Reads the surface temperature of the concrete or pavers. The system runs until the slab reaches the set temperature, then shuts off.', 'retrofit_compatible' => 'Can be installed after construction is complete—no need to break up or re-pour concrete.', 'compatible_controls' => 'The WarmlyYours snow melting controls that accept this sensor type.' }.freeze
Instance Attribute Summary collapse
-
#features ⇒ Object
readonly
Returns the value of attribute features.
-
#section_description ⇒ Object
readonly
Returns the value of attribute section_description.
-
#section_title ⇒ Object
readonly
Returns the value of attribute section_title.
-
#skus ⇒ Object
readonly
Returns the value of attribute skus.
Instance Method Summary collapse
-
#add_on_for(value) ⇒ Object
When a feature value is a Symbol, returns the matching add-on product hash { sku:, label: } so the template can render a product link.
-
#add_on_url(add_on) ⇒ Object
Returns the URL for an add-on product.
- #before_render ⇒ Object
- #feature_display_name(feature_key) ⇒ Object
-
#feature_tooltip(feature_key) ⇒ Object
Returns a short plain-text tooltip description for the given feature, or nil if none defined.
- #feature_value(product, feature_key) ⇒ Object
-
#initialize(skus: [], features: nil, section_title: nil, section_description: nil, sort_by_price: true, image_height: 180, image_overrides: {}) ⇒ ProductCompareTableComponent
constructor
A new instance of ProductCompareTableComponent.
- #sensor_feature?(feature_key) ⇒ Boolean
Methods inherited from ApplicationComponent
#cms_link, #fetch_or_fallback, #image_asset_tag, #image_tag, #number_to_currency, #number_with_delimiter, #post_path, #post_url, #strip_tags
Constructor Details
#initialize(skus: [], features: nil, section_title: nil, section_description: nil, sort_by_price: true, image_height: 180, image_overrides: {}) ⇒ ProductCompareTableComponent
Returns a new instance of ProductCompareTableComponent.
319 320 321 322 323 324 325 326 327 328 |
# File 'app/components/www/product_compare_table_component.rb', line 319 def initialize(skus: [], features: nil, section_title: nil, section_description: nil, sort_by_price: true, image_height: 180, image_overrides: {}) @skus = Array(skus).compact @features = features @section_title = section_title @section_description = section_description @sort_by_price = sort_by_price @image_height = image_height @image_overrides = image_overrides super() end |
Instance Attribute Details
#features ⇒ Object (readonly)
Returns the value of attribute features.
18 19 20 |
# File 'app/components/www/product_compare_table_component.rb', line 18 def features @features end |
#section_description ⇒ Object (readonly)
Returns the value of attribute section_description.
18 19 20 |
# File 'app/components/www/product_compare_table_component.rb', line 18 def section_description @section_description end |
#section_title ⇒ Object (readonly)
Returns the value of attribute section_title.
18 19 20 |
# File 'app/components/www/product_compare_table_component.rb', line 18 def section_title @section_title end |
#skus ⇒ Object (readonly)
Returns the value of attribute skus.
18 19 20 |
# File 'app/components/www/product_compare_table_component.rb', line 18 def skus @skus end |
Instance Method Details
#add_on_for(value) ⇒ Object
When a feature value is a Symbol, returns the matching add-on product hash
{ sku:, label: } so the template can render a product link.
Returns nil for non-Symbol values.
354 355 356 357 358 |
# File 'app/components/www/product_compare_table_component.rb', line 354 def add_on_for(value) return nil unless value.is_a?(Symbol) ADD_ON_PRODUCTS[value] end |
#add_on_url(add_on) ⇒ Object
Returns the URL for an add-on product.
Prefers an on-page anchor (e.g. "#sensors") when set, otherwise falls back
to the product detail URL.
363 364 365 366 367 |
# File 'app/components/www/product_compare_table_component.rb', line 363 def add_on_url(add_on) return add_on[:anchor] if add_on[:anchor].present? helpers.catalog_link_for_sku(add_on[:sku]) end |
#before_render ⇒ Object
330 331 332 333 |
# File 'app/components/www/product_compare_table_component.rb', line 330 def before_render @products = load_products @features = detect_features if @features.blank? end |
#feature_display_name(feature_key) ⇒ Object
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
# File 'app/components/www/product_compare_table_component.rb', line 369 def feature_display_name(feature_key) case feature_key when 'automatic' 'Automatic' when 'outdoor_rated' 'Outdoor Rated' when 'moisture_sensor' 'Moisture Sensor' when 'air_temp_sensor' 'Air Temp Sensor' when 'slab_temp_sensor' 'Slab Temp Sensor' when 'digital_temp_readout' 'Digital Temp Readout' when 'adjust_after_run_time' 'Adjust After Run Time' when 'multi_zone_capability' 'Multi Zone Capability' when 'timer' 'Timer' when 'countdown_timer' 'Countdown Timer' when 'control_remotely_via_iphone_android_or_web_app' 'Control Remotely Via iPhone/Android/Web App' when 'max_load' 'Max Load' # Thermostat-specific features when 'wifi' 'WiFi-Enabled' when 'remote_support' 'Remote Troubleshooting/Updates' when 'voice_control' 'Voice Assistant Compatible' when 'programmable' 'Programmable' when 'schedules' 'Schedules' when 'floor_sensor_included' 'Floor Sensor Included' when 'display' 'Display' # Pipe Freeze Protection specific features when 'included_sensor' 'Included Sensor' when 'temperature_setpoint' 'Temperature Setpoint' when 'indicators' 'Status Indicators' when 'built_in_gfi' 'Built-in GFI Protection' when 'waterproofing' 'Enclosure Rating' when 'area_of_use' 'Area of Use' # Sensor-specific features when 'sensor_installation' 'Installation' when 'detects_precipitation' 'Detects Precipitation' when 'measures_air_temp' 'Measures Air Temperature' when 'measures_slab_temp' 'Measures Slab Temperature' when 'retrofit_compatible' 'Retrofit Compatible' when 'compatible_controls' 'Compatible Controls' else feature_key.to_s.humanize end end |
#feature_tooltip(feature_key) ⇒ Object
Returns a short plain-text tooltip description for the given feature, or nil if none defined.
343 344 345 |
# File 'app/components/www/product_compare_table_component.rb', line 343 def feature_tooltip(feature_key) # rubocop:disable Metrics/MethodLength FEATURE_TOOLTIPS[feature_key] end |
#feature_value(product, feature_key) ⇒ Object
335 336 337 338 339 340 |
# File 'app/components/www/product_compare_table_component.rb', line 335 def feature_value(product, feature_key) sku = product[:sku] # Use hash table lookup instead of nested case statements SKU_FEATURES.dig(sku, feature_key) || false end |
#sensor_feature?(feature_key) ⇒ Boolean
347 348 349 |
# File 'app/components/www/product_compare_table_component.rb', line 347 def sensor_feature?(feature_key) SENSOR_FEATURE_KEYS.include?(feature_key) end |