PageSourceSearch

https://shop.nait.ca/eSolution/js/basket/all.js?v=4.3.409.56

js nait.ca collected 2026-09-24 18:25:46 UTC 28,592 bytes, 797 lines download raw bytes

1/*! https://mths.be/cssescape v1.5.1 by @mathias | MIT license */
2;(function(root, factory) {
3	// https://github.com/umdjs/umd/blob/master/returnExports.js
4	if (typeof exports == 'object') {
5		// For Node.js.
6		module.exports = factory(root);
7	} else if (typeof define == 'function' && define.amd) {
8		// For AMD. Register as an anonymous module.
9		define([], factory.bind(root, root));
10	} else {
11		// For browser globals (not exposing the function separately).
12		factory(root);
13	}
14}(typeof global != 'undefined' ? global : this, function(root) {
15
16	if (root.CSS && root.CSS.escape) {
17		return root.CSS.escape;
18	}
19
20	// https://drafts.csswg.org/cssom/#serialize-an-identifier
21	var cssEscape = function(value) {
22		if (arguments.length == 0) {
23			throw new TypeError('`CSS.escape` requires an argument.');
24		}
25		var string = String(value);
26		var length = string.length;
27		var index = -1;
28		var codeUnit;
29		var result = '';
30		var firstCodeUnit = string.charCodeAt(0);
31		while (++index < length) {
32			codeUnit = string.charCodeAt(index);
33			// Note: there’s no need to special-case astral symbols, surrogate
34			// pairs, or lone surrogates.
35
36			// If the character is NULL (U+0000), then the REPLACEMENT CHARACTER
37			// (U+FFFD).
38			if (codeUnit == 0x0000) {
39				result += '\uFFFD';
40				continue;
41			}
42
43			if (
44				// If the character is in the range [\1-\1F] (U+0001 to U+001F) or is
45				// U+007F, […]
46				(codeUnit >= 0x0001 && codeUnit <= 0x001F) || codeUnit == 0x007F ||
47				// If the character is the first character and is in the range [0-9]
48				// (U+0030 to U+0039), […]
49				(index == 0 && codeUnit >= 0x0030 && codeUnit <= 0x0039) ||
50				// If the character is the second character and is in the range [0-9]
51				// (U+0030 to U+0039) and the first character is a `-` (U+002D), […]
52				(
53					index == 1 &&
54					codeUnit >= 0x0030 && codeUnit <= 0x0039 &&
55					firstCodeUnit == 0x002D
56				)
57			) {
58				// https://drafts.csswg.org/cssom/#escape-a-character-as-code-point
59				result += '\\' + codeUnit.toString(16) + ' ';
60				continue;
61			}
62
63			if (
64				// If the character is the first character and is a `-` (U+002D), and
65				// there is no second character, […]
66				index == 0 &&
67				length == 1 &&
68				codeUnit == 0x002D
69			) {
70				result += '\\' + string.charAt(index);
71				continue;
72			}
73
74			// If the character is not handled by one of the above rules and is
75			// greater than or equal to U+0080, is `-` (U+002D) or `_` (U+005F), or
76			// is in one of the ranges [0-9] (U+0030 to U+0039), [A-Z] (U+0041 to
77			// U+005A), or [a-z] (U+0061 to U+007A), […]
78			if (
79				codeUnit >= 0x0080 ||
80				codeUnit == 0x002D ||
81				codeUnit == 0x005F ||
82				codeUnit >= 0x0030 && codeUnit <= 0x0039 ||
83				codeUnit >= 0x0041 && codeUnit <= 0x005A ||
84				codeUnit >= 0x0061 && codeUnit <= 0x007A
85			) {
86				// the character itself
87				result += string.charAt(index);
88				continue;
89			}
90
91			// Otherwise, the escaped character.
92			// https://drafts.csswg.org/cssom/#escape-a-character
93			result += '\\' + string.charAt(index);
94
95		}
96		return result;
97	};
98
99	if (!root.CSS) {
100		root.CSS = {};
101	}
102
103	root.CSS.escape = cssEscape;
104	return cssEscape;
105
106}));
107	
108// We will place our functions into an object so that they can be referenced as required
109// (e.g. so we can 'off' them and 'on' a custom function instead
110//
111// Ensure we have a place to put our local (named) functions
112if (typeof esolution === 'undefined') 
113{
114	esolution = {};
115}
116if (typeof esolution.basket === 'undefined')
117{
118	esolution.basket = {};
119}
120if (typeof esolution.basket.hooks === 'undefined')
121{
122	esolution.basket.hooks = [];
123}
124
125esolution.basket.add_hook = function(event_name, closure) {
126	esolution.basket.hooks.push({
127		event_name: event_name,
128		closure: closure
129	});
130	return true
131};
132
133esolution.basket.trigger_hook = function(event_name, details) {
134	for(var i = 0; i < esolution.basket.hooks.length; i++)
135	{
136		var ebh = esolution.basket.hooks[i];
137		if(ebh.event_name === event_name)
138		{
139			ebh.closure( event_name, details );
140		}
141	}
142};
143
144esolution.basket.submit_form_cti_buy = function(event) {
145	event.preventDefault();
146
147	var form = $(event.target);
148
149	form.children(".cti_buy_button").each(function(index) {
150		// commented below lines :/
151		/*//if a link with cti_buy_button exists in a form, submit the form instead of following the link
152		$(".cti_buy_button").data('text', $(".cti_buy_button").html());					//Store the current button text for potential re-use
153		// csr43877 - NAIT is using a slider which clones elements so "this" might not be the element we find later. Search the same way we search while processing the response, and save the html for possible restoring later
154		let active_id = $(".cti_buy_button").attr('id'); // note: all elements with a class of cti_buy_button, even though there should be only one, should have the same ids:/
155		let active_elt = $('#' + active_id + '.cti_buy_button');
156		active_elt.data('text', $(".cti_buy_button").html());*/
157		$(this).data('text', $(this).html());
158		if ($(this).text() != '') {
159			$(this).html('<i class="fa fa-spinner fa-spin"></i> '+esolution.tr('Adding...'));
160		} else {
161			$(this).html('<i class="fa fa-spinner fa-spin"></i>');
162		}
163	});
164
165	var query_string = form.serialize(); // build our form data
166	query_string += "&view=Json&template=Json"; // append our template & view
167	$.ajax({
168		type: form.attr('method'),
169		url: '/Basket/ajaxAddItem',
170		data: query_string,
171		success: function(json)
172		{
173			$("body").trigger("cti.basket",new Array(json));
174            if(json?.errors?.length === 0 || (json?.errors?.length === 1 && json?.errors[0]?.length === 0)){
175                $("body").trigger("cti.basket/UpsellSuggestions",json);
176            }
177		},
178		error: function(json) {
179			$("body").trigger("cti.basket",new Array(json));
180		},
181		dataType: "json"
182	});
183};
184
185esolution.basket.clicked_cti_buy_button_clone = null;
186
187esolution.basket.on_click_cti_buy_button = function(event){
188	event.preventDefault();
189	esolution.basket.clicked_cti_buy_button_clone = $(this)[0].cloneNode(true); // DOM object
190	if ($(this).parents("form").data("validate") == undefined || $(this).parents("form").data("validate") == "1") {
191		// validate data attribute does not exist or exists and is set to "1"
192		$(this).parents("form")[0].requestSubmit();
193	}
194};
195
196// polyfill code for the JS function requestSubmit...
197// may be deleted in the future when the function requestSubmit() is available on all browsers
198(function(prototype) {
199	if (typeof prototype.requestSubmit == "function") return
200
201	prototype.requestSubmit = function(submitter) {
202		//console.log("prototype.requestSubmit substitute function ");
203		if (submitter) {
204			validateSubmitter(submitter, this)
205			submitter.click()
206		} else {
207			submitter = document.createElement("input")
208			submitter.type = "submit"
209			submitter.hidden = true
210			this.appendChild(submitter)
211			submitter.click()
212			this.removeChild(submitter)
213		}
214	}
215
216	function validateSubmitter(submitter, form) {
217		submitter instanceof HTMLElement || raise(TypeError, "parameter 1 is not of type 'HTMLElement'")
218		submitter.type == "submit" || raise(TypeError, "The specified element is not a submit button")
219		submitter.form == form || raise(DOMException, "The specified element is not owned by this form element", "NotFoundError")
220	}
221
222	function raise(errorConstructor, message, name) {
223		throw new errorConstructor("Failed to execute 'requestSubmit' on 'HTMLFormElement': " + message + ".", name)
224	}
225})(HTMLFormElement.prototype);
226
227esolution.basket.diff_basket_states = function( old_items, new_items )
228{
229	if( typeof old_items === 'undefined' ) old_items = new_items; //on first call the cart will be undefined, this will initialize the cart to the current state and trap future diffs
230	try {
231		// Let's construct basket item objects for everything on the left
232		for(var i = 0; i < old_items.length; i++)
233		{
234			old_items[i] = new BasketItem(old_items[i]); // re-assign with new Item object
235		}
236		// Next, construct basket item objects for everything on the right
237		for(var j = 0; j < new_items.length; j++)
238		{
239			new_items[j] = new BasketItem(new_items[j]);
240		}
241		// Now we want to calculate signatures of each indiviual item, compare them to right signatures, and assign to the appropriate array
242		var updates = [];
243		var inserts = [];
244		var same    = [];
245		for(var r = 0; r < new_items.length; r++)
246		{
247			// here we're iterating on the RIGHT (new) side of things
248			var right_ob 	= new_items[r];
249			var right 		= new_items[r].generateSignature();
250			// next we need to iterate on the LEFT inside of the RIGHT
251			if( old_items.length === 0 )
252			{
253				// we have NO old items, let's pop this into insert then
254				inserts.push({
255					signature: right,
256					item: right_ob,
257					index: r
258				});
259			}
260			else {
261				for(var l = 0; l < old_items.length; l++)
262				{
263					var left_ob = old_items[l];
264					var left 	= old_items[l].generateSignature();
265					if(left === right)
266					{
267						same.push( { signature: right, item: right_ob, index: r } );
268					}
269					else if( left_ob.generateSubSignature([ left_ob.item, left_ob.desc ]) === right_ob.generateSubSignature([ right_ob.item, right_ob.desc ]) )
270					{
271						var state = 'UPDATE';
272						if ( typeof left_ob.text !== 'undefined' && left_ob.text !== null && typeof right_ob.text !== 'undefined' && right_ob.text !== null )
273						{
274							// this MAY be a same item BUT with different text properties (ie, special order items, etc)
275							// let's iterate on some of the properties we have in TEXT instead
276							var left_text_keys 		= Object.keys(left_ob.text);
277							var right_text_keys 	= Object.keys(right_ob.text);
278							var left_opands 		= [];
279							var right_opands 		= [];
280							var common_properties 	= [];
281							// we want to iterate on the two objects to reliably match common propreties, to see if we can determine if they're related or not
282							for(var rightprop1 = 0; rightprop1 < right_text_keys.length; rightprop1++) {
283								var rkey = right_text_keys[rightprop1];
284								for(var leftprop1 = 0; leftprop1 < left_text_keys.length; leftprop1++) {
285									var lkey = left_text_keys[leftprop1];
286									if(rkey === lkey) {
287										common_properties.push(rkey);
288									}
289								}
290							}
291							if(common_properties.length > 0) {
292								// we have some common properties, let's iterate and build ourselves both the left and right common properties at the same time
293								// then we'll push our side-specific additional data into the array
294								for(var comprop1 = 0; comprop1 < common_properties.length; comprop1++)
295								{
296									left_opands.push(left_ob.text[common_properties[comprop1]]);
297									right_opands.push(right_ob.text[common_properties[comprop1]]);
298								}
299								left_opands.push(left_ob.item);
300								left_opands.push(left_ob.desc);
301								right_opands.push(right_ob.item);
302								right_opands.push(right_ob.desc);
303							}
304							if(left_opands.length > 0 && right_opands.length > 0)
305							{
306								// we now have something we can operate on!
307								var left_sig 	= left_ob.generateSubSignature( left_opands );
308								var right_sig 	= right_ob.generateSubSignature( right_opands );
309								if(left_sig === right_sig) {
310									state = 'UPDATE';
311								}
312								else if(left_sig !== right_sig) {
313									state = 'INSERT';
314								}
315							}
316						}
317						if(state === 'UPDATE') {
318							updates.push( { signature: right, item: right_ob, index: r } );
319						}
320						else if(state === 'INSERT') {
321							inserts.push( { signature: right, item: right_ob, index: r } );
322						}
323					}
324					else if(l === old_items.length-1)
325					{
326						// we've got our last item, let's check if we need to delete or not
327						var is_found = false;
328						for(var u1 = 0; u1 < updates.length; u1++)
329						{
330							if( updates[u1].signature === right )
331							{
332								is_found = true;
333							}
334						}
335						for(var i1 = 0; i1 < inserts.length; i1++)
336						{
337							if( inserts[i1].signature === right )
338							{
339								is_found = true;
340							}
341						}
342						for(var s1 = 0; s1 < same.length; s1++)
343						{
344							if( same[s1].signature === right )
345							{
346								is_found = true;
347							}
348						}
349						if( !is_found )
350						{
351							inserts.push( { signature: right, item: right_ob, index: r });
352						}
353					}
354				}
355			}
356		}
357
358		// let's now deal with the updates/inserts for items, and the same items, they're not in any of our RIGHT arrays, so we can quickly iterate like below
359		var deletes = [];
360		for(var i = 0; i < old_items.length; i++)
361		{
362			var left_signature 	= old_items[i].generateSignature();
363			var left_sub_signature 	= old_items[i].generateSubSignature(
364					[
365						old_items[i].item,
366						old_items[i].desc
367					]
368				);
369			var item_found			= false;
370			updates.forEach(function(update) {
371				if( update.signature === left_signature || update.signature === left_sub_signature )
372				{
373					item_found = true;
374				}
375			});
376			inserts.forEach(function(insert) {
377				if( insert.signature === left_signature || insert.signature === left_sub_signature )
378				{
379					item_found = true;
380				}
381			});
382			same.forEach(function(same) {
383				if( same.signature === left_signature || same.signature === left_sub_signature )
384				{
385					item_found = true;
386				}
387			});
388			if( !item_found )
389			{
390				deletes.push( { signature: left_signature, item: old_items[i] } );
391			}
392		}
393
394		inserts.forEach(function(insert) {
395			esolution.basket.trigger_hook('item.added', {
396				signature: insert.signature,
397				item: insert.item
398			});
399		});
400
401		updates.forEach(function(update) {
402			esolution.basket.trigger_hook('item.changed', {
403				signature: update.signature,
404				item: update.item
405			});
406		});
407
408		deletes.forEach(function(deleted) {
409			esolution.basket.trigger_hook('item.deleted', {
410				signature: deleted.signature,
411				item: deleted.item
412			});
413		});
414	} catch (e) {
415		console.log('caught error when trying to diff basket states, falling through to existing logic.', e);
416	}
417};
418
419esolution.basket.change_button_state = function ( button ) {
420
421	if(button.text() != '')
422	{
423		button.html(button.data('qty') +' '+esolution.tr("IN CART", ""));
424	}
425	else
426	{
427		button.html(button.data('text'));
428	}
429	button.css('background-color', '#228B22');
430	button.css('border-color', '#228B22');
431	button.css('color', '#FFFFFF');
432	button.css('background-image', 'none');
433};
434
435esolution.basket.on_cti_basket = function (e, json){
436	//store the last data we've updated the page with so that we can rerender without a server pull
437	esolution.basket.old_cart_data = esolution.basket.cart_data;
438	esolution.basket.cart_data = json;
439//pwo 2021, commendting the following lines, they are incorrect
440//on first call to this function the previous state of the cart will ALWAYS be undefined, the 
441//diff function must detect undefined and set it to the current cart to initialize the state of the cart for future diffs
442//	if( typeof esolution.basket.old_cart_data.items === 'undefined') {
443//		esolution.basket.old_cart_data.items = []; // to prevent nulled/undefined entries
444//	}
445	//Update the buy button on basket update event
446	//console.log(json);
447	//
448	// We'll roll through the events, then through 'regular' items
449	// It's possible to update the same button both times, if events are handled in the 'regular' basket
450	for( var i = 0; i < json.events.length; i++ )
451	{
452		var thisevent = json.events[i];
453		var button = $(".event-"+thisevent);
454		if(button.text() != '')
455		{
456			//button.html('On Itinerary');
457			button.html(esolution.tr('In Cart'));
458		}
459	}
460
461	esolution.basket.diff_basket_states( esolution.basket.old_cart_data.items, esolution.basket.cart_data.items );
462
463	//var items = esolution.basket.cart_data.items;
464	// Line above was commented and replace with line below. The items object will now be a copy, rather than, a reference to esolution.basket.cart_data.items.
465	// This is required for the quantity on the buy button when the basket has multiple items all with the same item number but not coalesced.
466	// Without the copy, when the for loop code below finishes, it alters the property "qty" in one of the items in the object esolution.basket.cart_data.items.
467	// With the copy, the "qty" property is not altered:)
468	var items = JSON.parse(JSON.stringify(esolution.basket.cart_data.items));
469	var aggregate_items = { };
470	var aggregate_items_arr = [];
471
472	for( var i = 0; i < items.length; i++ )
473	{
474		var item = items[i];
475		if( typeof( aggregate_items[ item.item ] ) === 'undefined' )
476		{
477			aggregate_items[ item.item ] = [];
478			aggregate_items[ item.item ][ item.used ] = item;
479		}
480		else if( typeof( aggregate_items[ item.item ][ item.used ] ) === 'undefined' )
481		{
482			aggregate_items[ item.item ][ item.used ] = item;
483		}
484		else
485		{
486			if (typeof(item.qty) == "number") {
487				aggregate_items[item.item][ item.used ].qty = parseInt(aggregate_items[item.item][ item.used ].qty) + item.qty;
488			} else if (typeof(item.qty) == "string") {
489				aggregate_items[item.item][ item.used ].qty = parseInt(aggregate_items[item.item][ item.used ].qty) + parseInt(item.qty);
490			}
491		}
492	}
493
494	for( var j = 0; j < Object.keys(aggregate_items).length; j++ )
495	{
496		var ji = Object.keys(aggregate_items)[j];
497		for( var k = 0; k < Object.keys(aggregate_items[ji]).length; k++ )
498		{
499			var ki = Object.keys(aggregate_items[ji])[k];
500			aggregate_items_arr.push( aggregate_items[ji][ki] );
501		}
502	}
503
504	var button;
505
506	for( var i = 0; i < aggregate_items_arr.length; i++ )
507	{
508		var item = aggregate_items_arr[i];
509		//change the button style to secondary, then change the button text
510		if (item.used=="") {
511			button = $("#" + CSS.escape(item.item) + ".cti_buy_button, " + // id selector is for legacy views
512				"." + CSS.escape(item.item) + ".cti_buy_button, " + // ***
513				"[data-item=" + CSS.escape(item.item) + "][data-used=''].cti_buy_button, " +
514				"[data-item=" + CSS.escape(item.item) + "]:not([data-used]).cti_buy_button");
515		} else if (item.used=="US") {
516			button = $("#" + CSS.escape(item.item) + ".cti_buy_button, " + // id selector is for legacy views
517				"." + CSS.escape(item.item) + ".cti_buy_button, " + // ***
518				"[data-item=" + CSS.escape(item.item) + "][data-used='US'].cti_buy_button");
519		}
520
521		if(!button.data('qty')){
522			button.data('qty', 0);
523		}
524		if(item.qty === button.data('qty')) { item.qty = 0; }
525		button.data('qty', button.data('qty')+parseInt(item.qty)); // in cases where quantity doesn't change, I presume nil is the answer, but just to be safe teh above line will reset the count to nil, so we don't duplicate falsely
526		esolution.basket.change_button_state( button );
527	}
528	$("input[name=qty]").val("1");
529	$('.buy-form').hide();
530	$('#cti-buy-form-wrapper').hide();
531	//}
532	//Reset the 'qty' data attributes so that the in cart quantity on the button does not double the next time an item is added
533	for( var i = 0; i < json.items.length; i++ )
534	{
535		var item = json.items[i];
536		button = $("#" + CSS.escape(item.item) + ".cti_buy_button," +
537			"." + CSS.escape(item.item) + ".cti_buy_button, " + // ***
538			"[data-item=" + CSS.escape(item.item) + "][data-used=''].cti_buy_button," +
539			"[data-item=" + CSS.escape(item.item) + "]:not([data-used]).cti_buy_button," +
540			"[data-item=" + CSS.escape(item.item) + "][data-used='US'].cti_buy_button");
541		if(button.data('qty')){
542			button.data('qty', 0);
543		}
544	}
545	//console.log(json);
546	//Update add to cart buttons for items with an error
547	for( var i = 0; i < json.errors.length; i++ )
548	{
549		var error = json.errors[i];
550
551		if (Object.keys(error).length > 0) { // this check is required as json.errors may be an array of length 1 with no data
552
553			if (error.used=="" || error.used=="NW" ) {
554				button = $("#" + CSS.escape(error.item) + ".cti_buy_button, " + // id selector is for legacy views
555					"." + CSS.escape(error.item) + ".cti_buy_button, " + // ***
556					"[data-item=" + CSS.escape(error.item) + "]:not([data-used]).cti_buy_button, " +
557					"[data-item=" + CSS.escape(error.item) + "][data-used=''].cti_buy_button");
558			} else if (error.used=="US") {
559				button = $("#" + CSS.escape(error.item) + ".cti_buy_button, " + // id selector is for legacy views
560					"." + CSS.escape(error.item) + ".cti_buy_button, " + // ***
561					"[data-item=" + CSS.escape(error.item) + "][data-used='US'].cti_buy_button");
562			}
563
564			if(button.text() != '')
565			{
566				// We can get here because we're validating a form, or because the item is out of stock. Let's see if we had "form":"some form" in the error response
567				if (typeof(error.form) !== 'undefined')
568				{
569					button.html('Validating...');
570				}
571				else
572				{
573					var qty_in_cart;
574					for( var i = 0; i < aggregate_items_arr.length; i++ )
575					{
576						var item = aggregate_items_arr[i];
577						if (item.item == error.item && item.used == error.used) {
578							qty_in_cart = button.data('qty')+parseInt(item.qty); // note: reesearch button.data('qty')
579						}
580					}
581					if (qty_in_cart) {
582						button.html(qty_in_cart + " " + esolution.tr("IN CART")); // possible: consider limit? onorder? price on button?
583					} else {
584						button.html("Out of Stock"); // The majority of errors are due to an item being out of stock - this should be improved once the checkout is rebuilt
585					}
586					//button.html(aggregate_items_arr[i]["qty"]+' '+tr("IN CART", "")+' Out of Stock');
587				}
588			}
589			else
590			{
591				button.html(button.data('text'));
592			}
593			button.css('background-color', '#B22222');
594			button.css('border-color', '#B22222');
595			button.css('color', '#FFFFFF');
596			button.css('background-image', 'none');
597			if( typeof error.form != 'undefined' )
598			{
599				esolution.basket.trigger_hook('form_validate', {
600					error: error,
601					form_toggler: document.querySelector('#cti-buy-form')
602				});
603				$("#cti-buy-form").html( error.form );
604				$("#cti-buy-form-wrapper").show();
605                $("body").css("overflow-y", "hidden");
606                //console.log("overflow-y hidden");
607
608				// let's see if we just inserted any scripts into the DOM, and if so we'll execute them
609				$("#cti-buy-form").find('script').each(function() {
610					let form_script = this.innerText;
611					eval(form_script)//run script inside form
612				});
613			}
614			else
615			{
616				$("#cti-buy-form-wrapper").fadeOut();
617                $("body").css("overflow-y", "visible");
618			}
619		}
620	}
621
622};
623
624esolution.basket.on_click_cti_waitlist_button = function(e) {
625	e.preventDefault();
626
627	// when the customer clicks the 'register for waitlist' button, we intercept the flow and do an ajax request to fetch the configured registration form
628	// the 'register' button must be in a form that includes the item (hidden input with name 'item')
629
630	//build our form data
631	var form = $(e.target).closest('form');
632	var query_string = form.serialize();
633	//append our template & view
634	query_string += "&view=Json&template=Json";
635	$.ajax({
636		type: form.attr('method'),
637		url: '/Basket/itemunavailable',
638		data: query_string,
639		success: function(json)
640		{
641			//console.log(json);
642			if (typeof(json.errors) !== 'undefined') {
643				let error = json.errors[0];
644				if (typeof(error.form) !== 'undefined') {
645					$("#cti-buy-form").html( error.form );
646					$("#cti-buy-form-wrapper").show();
647
648					// let's see if we just inserted any scripts into the DOM, and if so we'll execute them
649					$("#cti-buy-form").find('script').each(function() {
650						let form_script = this.innerText;
651						eval(form_script);
652					});
653				}
654			}
655			
656		},
657		error: function(json) {	//ERROR
658			//console.log("error");
659		},
660		dataType: "json"
661	});
662};
663
664
665esolution.basket.on_submit_cti_waitlist_registration = function(e) {
666	e.preventDefault();
667	var form = $(e.target).closest('form');
668	var query_string = form.serialize();
669	var url = form.attr('action');
670
671	console.log("Submitting waitlist registration");
672	$.ajax({
673		type: form.attr('method'),
674		url: url,
675		data: query_string,
676		success: function(json)
677		{
678			if( typeof(json.message) !== 'undefined') {
679				$("#cti-buy-form").html( json.message );
680				//$("#cti-buy-form-wrapper").show();
681			}
682		},
683		error: function(json) {	//ERROR
684			//console.log("error");
685		},
686		dataType: "json"
687	});
688	
689};
690
691
692
693esolution.basket.on_refresh_count_event = function() {
694	$.ajax({
695		type: "GET",
696		url: '/Basket/json?view=Json&template=Json',
697		success: function(json) {
698			$("body").trigger("cti.basket", new Array(json));
699		},
700		dataType: "json"
701	});
702};
703
704esolution.basket.on_click_cti_remove_button = function(e) {
705//if a link with cti_buy_button exists in a form, submit the form instead of following the link
706	e.preventDefault();
707	var name = $(this).attr('name');
708	$.ajax({
709		type: "GET",
710		url: '/Basket/ajaxRemoveIndex?view=Json&template=Json',
711		data: {
712			index:name
713		},
714		success: function(json)
715		{
716			$("body").trigger("cti.basket",new Array(json));
717			$('.'+CSS.escape(name)).css('background-color', '');
718			$('.'+CSS.escape(name)).css('color', '');
719		}
720	});
721};
722
723
724esolution.basket.on_click_cti_buy_form_close = function(e){
725	e.preventDefault();
726	$("#cti-buy-form-wrapper").hide();
727    window.location.reload();
728
729		/* NOTE: this is only correct under limited conditions. The cti_buy_form popup is used for much more than buy forms, most of which do not have any such id  */
730	// Note: The item number appears to be one of the classes in the class html attribute.
731	let selector_id = (esolution.basket.clicked_cti_buy_button_clone.id ? "#" + esolution.basket.clicked_cti_buy_button_clone.id : "");
732	let selector_classes = "." + esolution.basket.clicked_cti_buy_button_clone.className.split(" ").join(".");
733	button = $(selector_id ? selector_id : "" + selector_classes ? selector_classes : "");
734	// Upon closing the form and not submitting form, there are two options...
735	// Option 1 ... Write "Item Unavailable" on button(s) with the same classes as the button clicked.
736	//button.html("Item Unavailable");
737	// Option 2... Reset button(s) with the same classes as the button clicked to the state before the button was clicked.
738	button.html(esolution.basket.clicked_cti_buy_button_clone.innerHTML);
739	button.css('background-color', esolution.basket.clicked_cti_buy_button_clone.style.backgroundColor);
740	button.css('border-color', esolution.basket.clicked_cti_buy_button_clone.style.borderColor);
741	button.css('color', esolution.basket.clicked_cti_buy_button_clone.style.color);
742	button.css('background-image', esolution.basket.clicked_cti_buy_button_clone.style.backgroundImage);
743
744	if( esolution.basket.cart_data.errors.length > 0 ) {
745		esolution.basket.cart_data.errors = [];
746	}
747	esolution.basket.update();
748};
749
750
751
752
753$(function(){
754
755	//on load pull the ajax basket and trigger a refresh
756	/*$.ajax({
757		url:"/Basket/json?template=Json",
758		success: function(json)
759		{
760			$("body").trigger("cti.basket",new Array(json));
761		},
762		dataType: "json"
763	});*/
764
765	//bind all forms named cti_buy to an ajax post
766	$("body").on("submit","form[name=cti_buy]",esolution.basket.submit_form_cti_buy);
767
768	//if a link with cti_buy_button exists in a form, submit the form instead of following the link
769	$(".cti_buy_button").on("click",esolution.basket.on_click_cti_buy_button);
770
771	//Update the buy button on basket update event
772	$("body").on("cti.basket",esolution.basket.on_cti_basket);
773
774	$("body").on("refreshCountEvent", esolution.basket.on_refresh_count_event);
775
776	//if a link with cti_buy_button exists in a form, submit the form instead of following the link
777	$("body").on("click",".cti_remove_button",esolution.basket.on_click_cti_remove_button);
778
779	$("body").on("click",".cti-buy-form-close",esolution.basket.on_click_cti_buy_form_close);
780
781	// attach to all "unavailable" buttons to load a form which will be loaded into the cti-buy-form-wrapper / cti-buy-form
782	// this implements the waitlist registration form, and the submit action on that form
783	$("body").on("click",".cti_waitlist_button",esolution.basket.on_click_cti_waitlist_button);
784	$("body").on("click",".cti_waitlist_close_button",esolution.basket.on_click_cti_buy_form_close);
785	$("body").on("click",".cti_waitlist_registration_submit",esolution.basket.on_submit_cti_waitlist_registration)
786
787	//append a container for inserting forms to the bottom of the dom, if length of matching elements is 0 (false) (eg CourseSearchView includes its own wrapper div). If it exists, we'll use it
788	if( !$('div#cti-buy-form-wrapper').length ) {
789		$('body').append("<div id='cti-buy-form-wrapper'><a href='#' class='cti-buy-form-close'><i class='fa fa-times-circle fa-fw'></i></a><div id='cti-buy-form'></div></div>");
790	}
791/*
792		// sample from course search view
793         <div id="cti-buy-form-wrapper" style="display:none"><div style="float:right;width:30px;text-align:right; padding: 2px 10px 2px 0px;"><a href="#" class="cti-buy-form-close"><i class="fa fa-times-circle fa-fw"></i></a></div>
794          <div id="cti-buy-form"></div></div>
795*/
796
797});

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.