PageSourceSearch

https://cedar.carmel.ac.uk/Static/Lib/common.js?v=2026r2.3

js carmel.ac.uk collected 2026-10-04 13:33:54 UTC 12,105 bytes, 445 lines download raw bytes

1/**
2 * If a variable (likely a function parameter) is undefined / wasn't supplied, then
3 * coalesce to a different value.
4 * @param arg
5 * @param defaultToVal
6 * @returns {*}
7 */
8function defaultTo(arg, defaultToVal) {
9	return typeof arg !== 'undefined' ? arg : defaultToVal;
10}
11
12// See https://stackoverflow.com/questions/6756583/prevent-browser-from-loading-a-drag-and-dropped-file/6756680#6756680
13function disableBrowserFilePreview() {
14	
15	window.addEventListener("dragover", function(e){
16		e = e || event;
17		e.preventDefault();
18	}, false);
19	
20	window.addEventListener("drop", function(e){
21		e = e || event;
22		e.preventDefault();
23	}, false);
24}
25
26
27/**
28 * Implementation of Java's String.hashCode() in JS.
29 * http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
30 * @param absolute
31 * @returns {number}
32 */
33String.prototype.hashCode = function(absolute){
34	
35	absolute = defaultTo(absolute, false);
36	
37	var hash = 0;
38	if (this.length === 0) return hash;
39	for (let i = 0; i < this.length; i++) {
40		let char = this.charCodeAt(i);
41		hash = ((hash<<5)-hash)+char;
42		hash = hash & hash; // Convert to 32bit integer
43	}
44	
45	if (absolute) {
46		hash = Math.abs(hash);
47	}
48	return hash;
49};
50
51
52/**
53 * Recursively build a HTML form compatible key-value set from a potentially multidimensional
54 * key-value set
55 * @param string name POST key/name.
56 * @param mixed val POST value, which may be an object or array.
57 * @returns {unresolved}
58 */
59function buildHTMLFormAttributes(name, val) {
60	var postKeyVals = {};
61	if (typeof val === 'string') {
62		postKeyVals[name] = val;
63	} else {
64		$.each(val	, function(valEntryName, valEntryVal) {
65			$.extend(
66				postKeyVals, 
67				buildHTMLFormAttributes(
68					name+'['+valEntryName+']', 
69					valEntryVal
70				)
71			);
72		});
73	}
74
75	return postKeyVals;
76}
77
78/**
79 * Create a deep-clone of a simple object.
80 *
81 * Should only be used for simple JSON-compliant objects and shouldn't be used for objects with prototypes,
82 * functions or circular references.
83 * @return Object
84 */
85function deepClone(yourObj) {
86	return JSON.parse(JSON.stringify(yourObj));
87}
88
89/**
90 * Toggle browser fullscreen, if it's available.
91 * Taken from: https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API#Toggling_fullscreen_mode
92 */
93function toggleFullScreen(forceEnable) {
94
95	forceEnable = defaultTo(forceEnable, null);
96
97	var doc = window.document;
98	var docEl = doc.documentElement;
99
100	var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
101	var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
102
103	if (forceEnable === null) {
104		forceEnable = !doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement
105	}
106
107	if(forceEnable) {
108		requestFullScreen.call(docEl);
109	} else {
110		cancelFullScreen.call(doc);
111	}
112}
113
114/**
115 * Very simple pluralizer.
116 */
117function pluralize(word, quantity) {
118	return quantity + ' ' + (quantity === 1 ? word : word + 's');
119}
120
121/**
122 *
123 * @param {Date} [date]
124 */
125function getWelcomeMessage(date) {
126	date = defaultTo(date, new Date())
127
128	var hours = date.getHours();
129	if (hours < 12) {
130		return 'Good morning';
131	} else if (hours < 17) {
132		return 'Good afternoon';
133	} else {
134		return 'Good evening';
135	}
136}
137
138function round(num, decimalPlaces) {
139	decimalPlaces = defaultTo(decimalPlaces, 2);
140	var coefficient = Math.pow(10, decimalPlaces);
141 	return (Math.round(num * coefficient) / coefficient);
142}
143
144
145function formatDate(date, incDate, incTime) {
146
147	incDate = defaultTo(incDate, true);
148	incTime = defaultTo(incTime, true);
149
150	if (!(date instanceof Date) || (!incDate && !incTime)) {
151		return null;
152	}
153
154	var opts = {};
155
156	if (incDate) {
157		Object.assign(opts, {day: 'numeric', month: 'short', year:'numeric'});
158	}
159	if (incTime) {
160		Object.assign(opts, {hour:'2-digit', minute:'2-digit'});
161	}
162
163	return date.toLocaleString('en-GB', opts);
164}
165
166/**
167 * Convert time string to equivalent total number of seconds. E.g. "16:30" -> 59400. 
168 * An invalid time string e.g. "25:30" would be rejected.
169 * @param {string} timeString e.g. "16:30", or "4:15" etc.
170 * @returns {?int} Total seconds, or NULL if supplied arg is invalid.
171 */
172function timeToSeconds(timeString) {
173	const [hours, minutes] = timeString.split(':').map(val => parseInt(val));
174	// Values which are not-a-number will fail this check:
175	if (!(hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59)) {
176		return null;
177	}
178	return (hours * 3600) + (minutes * 60);
179}
180
181
182/**
183 * Convert HTML metacharacters into to their HTML entities.
184 * @param {string} str
185 * @return {string}
186 */
187function escapeHtml(str) {
188	var entityMap = {
189		'&': '&amp;',
190		'<': '&lt;',
191		'>': '&gt;',
192		'"': '&quot;',
193		"'": '&#39;',
194		'/': '&#x2F;',
195		'`': '&#x60;',
196		'=': '&#x3D;'
197	};
198
199	return String(str).replace(/[&<>"'`=\/]/g, function(s) {
200		return entityMap[s];
201	});
202}
203
204/**
205 * Return string to given length + ellipsis.
206 * @todo Enhance to prevent word breaking.
207 * @param {string} str
208 * @param {number} len
209 * @return {string}
210 */
211function makeNiceString(str, len) {
212	if (str.length <= len) {
213		return str;
214	} else {
215		return str.substr(0, len - 3) + '...';
216	}
217}
218
219/**
220 * Parse a PHP or JS JSON encoded DateTime value and return a corresponding JS Date object.
221 * @param {(Object|string)} d
222 */
223function parseDate(d) {
224	// Encoded from JS via JSON.stringify
225	if (typeof d === 'string') {
226		return new Date(d);
227	// Encoded from PHP via json_encode:
228	} else {
229		// return new Date(Date.parse(d.date));
230		// Altered for Safari support. See https://stackoverflow.com/questions/5324178
231		var arr = d.date.split(/[- :]/);
232		return new Date(arr[0], arr[1]-1, arr[2], arr[3], arr[4], arr[5]);
233	}
234
235}
236
237/**
238 * See https://stackoverflow.com/a/14706877
239 * @param {*} val
240 * @return {boolean} TRUE if the argument is an Object or an Array.
241 */
242function isObject(val) {
243	var type = typeof val;
244	return type === 'function' || type === 'object' && !!val;
245}
246
247/**
248 * Upper-case the first character in a string.
249 * @param {string} str
250 * @return {string}
251 */
252function ucFirst(str) {
253	return str.charAt(0).toUpperCase() + str.substring(1);
254}
255
256/**
257 * Remove HTML from a string.
258 * Similar to `strip_tags(..)` in PHP.
259 * Source: https://stackoverflow.com/a/47140708
260 * @param {string} html e.g. "<p>Hello</p>"
261 * @return {string} e.g. "Hello"
262 */
263function stripTags(html) {
264   var doc = new DOMParser().parseFromString(html, 'text/html');
265   return doc.body.textContent || "";
266}
267
268function setClipboard(text){
269	var input = document.createElement('input');
270	document.body.appendChild(input);
271	input.value = text;
272	input.select();
273	document.execCommand('copy', false);
274	input.remove();
275}
276
277function createTooltip(tooltipProps) {
278	const {id, heading, content, allowHtml, hideMobile, delayMs, hideOnBlur, autoload, maxWidth} = tooltipProps;
279
280	// Optionally hide on mobile:
281	if (isMobileDevice() && hideMobile) {
282		return;
283	}
284
285	let title = heading ? "<p class='tippy-header'>" + heading + "</p>" : '';
286	
287	let contentWithTitle = 
288		"<div class='tippy-wrapper'>" +
289			title +
290			"<div class='tippy-inner-content'>" + content + "</div>" +
291		"</div>";
292
293	let parent = $("#" + id).parent();
294	
295	// Don't apply tooltip if the wrapper is tablesorter - this creates duplicates!
296	if (parent.hasClass("tablesorter-header-inner")) {
297		return;
298	}
299
300	// Set tooltip props:
301	parent.attr('data-tippy-content', contentWithTitle);
302	parent.attr('data-tippy-allowHTML', allowHtml);
303	parent.attr('data-tippy-delay', delayMs);
304	parent.attr('data-tippy-hideOnClick', hideOnBlur);
305	parent.attr('data-tippy-showOnCreate', autoload);
306	parent.attr('data-tippy-maxWidth', maxWidth);
307	parent.attr('aria-label', heading);
308}
309
310/**
311 * @return {number} Time in milliseconds since initial page load.
312 */
313function uptime() 
314{
315	// List of performance types - see https://developer.mozilla.org/en-US/docs/Web/API/PerformanceEntry/entryType
316	const [currPage] = performance.getEntriesByType("navigation");
317	return (performance.now() - currPage.startTime);
318}
319
320
321/**
322 * If `arrOrVal` is an array, return it as it is.
323 * If `arrOrVal` is anything else, box it inside an array and return that.
324 *
325 * @param {*|Array} arrOrVal
326 */
327Array.box = function(arrOrVal) {
328	if (Array.isArray(arrOrVal)) {
329		return arrOrVal;
330	} else {
331		return [arrOrVal];
332	}
333};
334
335// Based on addHours from: https://stackoverflow.com/a/1050782
336Date.prototype.addMinutes = function(m) {
337   this.setTime(this.getTime() + (m*60*1000));
338   return this;
339};
340
341
342/** IE11 Polyfills: **/
343
344/*
345  `Element.closest` https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
346*/
347
348if (!Element.prototype.matches) {
349  Element.prototype.matches = Element.prototype.msMatchesSelector || 
350                              Element.prototype.webkitMatchesSelector;
351}
352
353if (!Element.prototype.closest) {
354  Element.prototype.closest = function(s) {
355    var el = this;
356
357    do {
358      if (el.matches(s)) return el;
359      el = el.parentElement || el.parentNode;
360    } while (el !== null && el.nodeType === 1);
361    return null;
362  };
363}
364
365/*
366   `NodeList.forEach` https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
367 */
368if (window.NodeList && !NodeList.prototype.forEach) {
369   NodeList.prototype.forEach = Array.prototype.forEach;
370}
371
372/* 
373   `ChildNode.remove` https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/remove#Polyfill
374 */
375(function (arr) {
376  arr.forEach(function (item) {
377    if (item.hasOwnProperty('remove')) {
378      return;
379    }
380    Object.defineProperty(item, 'remove', {
381      configurable: true,
382      enumerable: true,
383      writable: true,
384      value: function remove() {
385        if (this.parentNode === null) {
386          return;
387        }
388        this.parentNode.removeChild(this);
389      }
390    });
391  });
392})([Element.prototype, CharacterData.prototype, DocumentType.prototype]);
393
394
395// Function to load the image when it's about to come into the viewport
396const lazyLoadImages = () => {
397  const images = document.querySelectorAll('img.lazy'); 
398  const observer = new IntersectionObserver((entries, observer) => {
399    entries.forEach(entry => {
400      if (entry.isIntersecting) {
401        const img = entry.target;
402		const cacheBuster = new Date().getTime(); // or a server-side version string
403        const imgSrc = img.getAttribute('data-src'); 
404        if (imgSrc) {
405          // Add a fallback for broken or missing images
406          img.onerror = () => {
407            img.src = '/images/placeholder.jpg'; // ✅ path to your default placeholder
408            img.classList.add('broken-image');
409          };
410
411          // Trigger actual image load with cache-busting param
412          img.src = `${imgSrc}?v=${cacheBuster}`;
413        }
414        img.classList.remove('lazy'); 
415        observer.unobserve(img);
416      }
417    });
418  }, { threshold: 0.1 }); // Load earlier (10% visible)
419
420  images.forEach(img => observer.observe(img));
421
422  // 👇 Watch for dynamically added images
423  const mutationObserver = new MutationObserver((mutations) => {
424    mutations.forEach(mutation => {
425      mutation.addedNodes.forEach(node => {
426        if (node.nodeType === 1) {
427          if (node.tagName === 'IMG' && node.classList.contains('lazy')) {
428            observer.observe(node);
429          } else {
430            // Check inside the added node (e.g., in modal)
431            node.querySelectorAll && node.querySelectorAll('img.lazy').forEach(img => observer.observe(img));
432          }
433        }
434      });
435    });
436  });
437
438  mutationObserver.observe(document.body, {
439    childList: true,
440    subtree: true
441  });
442};
443
444// Run on page load
445document.addEventListener('DOMContentLoaded', lazyLoadImages);

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.