PageSourceSearch

https://suitor.com.au/wp-content/themes/suitor_v1/assets/au-backup-length.js?ver=1788453536

js suitor.com.au collected 2026-10-04 20:10:47 UTC 7,623 bytes, 182 lines download raw bytes

1/**
2 * Backup Suit Size Visibility + Length Visibility — standalone add-on.
3 *
4 * Loaded as a separate <script> AFTER the main Vite bundle, because the
5 * committed/live bundle cannot be reproduced from repo sources (src<->prod
6 * drift), so we must not rebuild/replace it. All behaviour here is self-contained
7 * jQuery on WordPress' global jQuery — it does not depend on bundle internals.
8 *
9 * - Length visibility: fills the server-rendered hidden <span data-length-garment>
10 *   placeholders from the customer-height field. On the cart page there is no
11 *   height input, so the value comes from the checkout form's sessionStorage copy.
12 * - Backup preview: on the product page, previews the backup size (next option in
13 *   the size <select>; previous one when the largest is selected).
14 *
15 * The backup SIZE lines in cart/checkout/minicart/thank-you are rendered
16 * server-side (PHP) and need no JS. With more than one suit in the cart the
17 * server renders no placeholders at all, so this script is a no-op there.
18 */
19(function ($) {
20	'use strict';
21
22	if (typeof $ === 'undefined') {
23		return;
24	}
25
26	// --- Length visibility -------------------------------------------------
27
28	// Mirrors PHP suitor_get_assigned_length() in inc/woocommerce-customisation/length-visibility.php.
29	function assignedLength(garment, heightValue) {
30		var raw = String(heightValue == null ? '' : heightValue).trim();
31		if (!/^\d+$/.test(raw)) return null;
32		var height = parseInt(raw, 10);
33		if (height < 20 || height > 250) return null;
34		if (garment === 'jacket') return height >= 188 ? 'Long' : 'Regular';
35		if (garment === 'trousers') {
36			if (height >= 188) return 'Long';
37			return height >= 177 ? 'Regular' : 'Short';
38		}
39		return null;
40	}
41
42	// The checkout form (incl. height) is persisted to sessionStorage on every
43	// change; the cart page has no height input, so lengths there are resolved
44	// from that stored copy. A corrupt entry must not break the rest of init.
45	function storedCheckoutHeight() {
46		try {
47			var formStr = sessionStorage.getItem('checkoutForm');
48			if (!formStr) return '';
49			var parsed = JSON.parse(formStr);
50			if (!Array.isArray(parsed)) return '';
51			for (var i = 0; i < parsed.length; i++) {
52				if (parsed[i] && parsed[i].name === 'billing_customer_height') {
53					return parsed[i].value;
54				}
55			}
56			return '';
57		} catch (e) {
58			return '';
59		}
60	}
61
62	function updateLengthVisibility() {
63		var $input = $('input[name="billing_customer_height"]');
64		var height = $input.length ? $input.val() : storedCheckoutHeight();
65		$('span[data-length-garment]').each(function () {
66			var $span = $(this);
67			var length = assignedLength($span.attr('data-length-garment'), height);
68			// The placeholder carries an info icon as a sibling of the value span —
69			// write into .js-length-value, never into the wrapper, or the icon dies.
70			var $value = $span.find('.js-length-value');
71			var $target = $value.length ? $value : $span;
72			if (length === null) {
73				$target.text('');
74				$span.attr('hidden', 'hidden');
75			} else {
76				$target.text(' ' + length);
77				$span.removeAttr('hidden');
78			}
79			// Keep the row's native tooltip in sync with the shown length.
80			// data-title-base caches the original so this stays idempotent.
81			var $line = $span.closest('p[title]');
82			if ($line.length) {
83				if (typeof $line.attr('data-title-base') === 'undefined') {
84					$line.attr('data-title-base', $line.attr('title') || '');
85				}
86				var baseTitle = $line.attr('data-title-base');
87				$line.attr('title', length === null ? baseTitle : (baseTitle + ' ' + length).replace(/^\s+|\s+$/g, ''));
88			}
89		});
90	}
91
92	// --- Backup size preview (product page) --------------------------------
93
94	// Next option in the select; previous one when the largest is selected.
95	function updateBackupSizePreview($form) {
96		var $box = $form.find('.js-backup-size-preview');
97		if (!$box.length) return;
98		var entries = [];
99		$form.find('select[name^="size["]').each(function () {
100			var $select = $(this);
101			var name = ($select.attr('name') || '').toLowerCase();
102			if (name.indexOf('length') !== -1) return;
103			if (name.indexOf('jacket') === -1 && name.indexOf('trouser') === -1) return;
104			var $options = $select.find('option').filter(function () {
105				return $(this).attr('value');
106			});
107			var idx = $options.index($select.find('option:selected'));
108			if (idx === -1 || $options.length < 2) return;
109			var backupIdx = idx === $options.length - 1 ? idx - 1 : idx + 1;
110			// option value format: "M/40|Jacket/Shirt"
111			var parts = $options.eq(backupIdx).attr('value').split('|');
112			if (parts.length < 2) return;
113			// Composite bundles name the attribute 'Jacket/Shirt', so the row needs
114			// ' Size' appended. Simple products already call it 'Jacket Size' —
115			// appending there produced 'Backup Jacket Size Size'.
116			var label = parts[1].replace('Jacket/Shirt', 'Jacket');
117			entries.push({ label: label, needsSize: !/\s*size\s*$/i.test(label), size: parts[0] });
118		});
119		// Build DOM with .text() so option values can never inject markup.
120		$box.empty();
121		if (entries.length) {
122			entries.forEach(function (e) {
123				$('<span class="d-block"></span>')
124					.text('Backup ' + e.label + (e.needsSize ? ' Size' : '') + ': ' + e.size)
125					.appendTo($box);
126			});
127		} else {
128			// The card is always visible now, so it needs a resting state
129			// instead of being hidden until sizes are picked.
130			$('<span class="d-block backupsuit-placeholder"></span>')
131				.text('Select your sizes above')
132				.appendTo($box);
133		}
134	}
135
136	// --- Bindings ----------------------------------------------------------
137
138	// The customer-height field is a plain <input>, so real typing fires native
139	// input/change events that our (global) jQuery hears. Delegated for safety.
140	$(document).on('input change', 'input[name="billing_customer_height"]', updateLengthVisibility);
141
142	// The cart table is replaced wholesale by WooCommerce AJAX (quantity change,
143	// item removal); the fresh markup needs filling again.
144	$(document.body).on('updated_wc_div updated_cart_totals', updateLengthVisibility);
145
146	// The size <select> is wrapped in the theme's custom-select widget. When a
147	// user picks an option the theme calls `$select.trigger('change')` on ITS OWN
148	// bundled jQuery instance — a synthetic event our separate jQuery never hears,
149	// and no native change is dispatched. So listen for the native click on the
150	// styled dropdown option instead. Capture phase: the theme's own li handler
151	// calls stopPropagation(), which would kill a bubbling listener. Defer the
152	// read so it runs after the theme has updated the underlying <select> value.
153	document.addEventListener('click', function (e) {
154		var li = e.target && e.target.closest ? e.target.closest('ul.select-options li') : null;
155		if (!li) return;
156		var form = li.closest('form.addtocart-form');
157		if (!form) return;
158		setTimeout(function () { updateBackupSizePreview($(form)); }, 0);
159	}, true);
160
161	// Fallback for any path that does dispatch a native change on the select.
162	$(document).on('change', 'form.addtocart-form select[name^="size["]', function () {
163		updateBackupSizePreview($(this).closest('form'));
164	});
165
166	function init() {
167		updateLengthVisibility();
168		$('form.addtocart-form').each(function () {
169			updateBackupSizePreview($(this));
170		});
171	}
172
173	$(function () { init(); });
174	// Re-run after full load and shortly after, to catch the bundle's
175	// sessionStorage restore of the height field (which may setDate/val without
176	// firing an event we can hear).
177	$(window).on('load', function () {
178		init();
179		setTimeout(init, 500);
180		setTimeout(init, 1200);
181	});
182})(window.jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.