Move JS code out of .ts


#1

Hi,

I have a very large block of javascript code (a part of it is below) in one of my .ts files for the rendering of an SVG map and it is causing an enormous drop in performance.

I think I need to move it all outside the .ts into a separate .js file. In principe, I should be able to simply insert a script tag into the header of index.html such as <script src="assets/js/test.js"></script> and then call individual variables and functions using declare var x or declare function y. However this piece of code relies on this structure : $.fn.wayfinding = function. I have no idea how to move the code outside and then ensure it is executed just once.

I’ve also tried calling the code in an external .js via import * as wayfinding from ‘…/…/assets/js/wayfinding.js’; or import * as wayfinding from ‘…/…/assets/js/wayfinding’; For the latter, the declaration is seen but nothing is executed.

Can anyone help?

var defaults = {
			'path': {
				color: 'red',
				radius: 1, 
				speed: 1, 
				width: 8 
			},
			'endpoint': false,
			'dataStoreCache': null
		},
		dataStore;

		$.fn.wayfinding = function (action, options, callback) {
			var passed = options,
				obj, 
				maps,
				defaultMap, 
				startpoint, 
				portalSegments = [], 
				solution,
				result, 
				drawing;
				
			function build() {
	
				dataStore = {
					'p': [], 
					'q': [] 
				};
	
				portalSegments = [];
	
				$.each(maps, function(i, map) {
					buildDataStore(i, map, map.el);
				});
	
				buildPortals();
				generateRoutes();
	
				return dataStore;
			}
			
			function initializePanZoom(el) {
				
				el.panzoom({
					minScale: 1,
					maxScale: 14,
					exponential: false,
					duration: 100,
					contain: 'invert',
					increment: 3,
					$zoomIn: $("button.zoom-in"),
					$zoomOut: $("button.zoom-out"),
					$reset: $("button.reset")
				});
				
				el.on('panzoomzoom', function(e, panzoom, scale) {
					console.log(scale);
				});
	
				el.find('a').on('mousedown touchstart', function(e) {
					e.stopImmediatePropagation();
				});
				
			}
			
			function getOptions(el) {
				
				var optionsPrior = el.data('wayfinding:options');
	
				drawing = el.data('wayfinding:drawing'); 
	
				options = $.extend(true, {}, defaults, options);
	
				if (optionsPrior !== undefined) {
					options = optionsPrior;
				} else {
					options = $.extend(true, {}, defaults, options);
				}
	
				options = $.metadata ? $.extend(true, {}, options, el.metadata()) : options;
	
				maps = options.maps;
	
				if (typeof options.defaultMap === 'function') {
					defaultMap = options.defaultMap();
				} else {
					defaultMap = options.defaultMap;
				}
	
				if (typeof options.startpoint === 'function') {
					startpoint = options.startpoint();
				} else {
					startpoint = options.startpoint;
				}
			}
			
			function setOptions(el) {
	
				el.data('wayfinding:options', options);
				el.data('wayfinding:drawing', drawing);
				el.data('wayfinding:data', dataStore);
			}
			
			function hidePath(el) {
				$('path[class^=directionPath]', el).css({
					'stroke': 'none'
				});
			}

			
	
			function routeTo(destination, el) {
				var i,
					draw,
					stepNum,
					level,
					reversePathStart,
					portalsEntered,
					lastStep,
					ax,
					ay,
					bx,
					by,
					aDX,
					aDY,
					bDX,
					bDY,
					cx,
					cy,
					px,
					py,
					curve,
					nx,
					ny,
					thisPath,
					pick;
	
				options.endpoint = destination;
	
				$('path[class^=directionPath]', obj).remove();
				$('#Rooms *.wayfindingRoom', obj).removeAttr('class');
	
				solution = [];
	
				if (startpoint !== destination) {
	
					$('#Rooms a[id="' + startpoint + '"]', obj).attr('class', 'wayfindingRoomStart');
					$('#Rooms a[id="' + destination + '"]', obj).attr('class', 'wayfindingRoom');
						
					solution = getShortestRoute();
	
					if (reversePathStart !== -1) {
	
						portalsEntered = 0;
						for (i = 0; i < solution.length; i++) {
							if (solution[i].type === 'po') {
								portalsEntered++;
							}
						}
	
						//break this into a new function?
						drawing = new Array(portalsEntered); // Problem at line 707 character 40: Use the array literal notation [].
	
						drawing[0] = [];
	
						//build drawing and modify solution for text generation by adding .direction to solution segments?
	
						draw = {};
	
						if(solution.length === 0) {
							console.warn('Attempting to route with no solution. This should never happen. SVG likely has errors. Destination is: ' + destination);
							return;
						}
	
						//if statement incorrectly assumes one door at the end of the path, works in that case, need to generalize
						if (dataStore.p[solution[0].floor][solution[0].segment].d[0] === startpoint) {
							draw = {};
							draw.floor = solution[0].floor;
							draw.type = 'M';
							draw.x = dataStore.p[solution[0].floor][solution[0].segment].x;
							draw.y = dataStore.p[solution[0].floor][solution[0].segment].y;
							draw.length = 0;
							drawing[0].push(draw);
							draw = {};
							draw.type = 'L';
							draw.floor = solution[0].floor;
							draw.x = dataStore.p[solution[0].floor][solution[0].segment].m;
							draw.y = dataStore.p[solution[0].floor][solution[0].segment].n;
							draw.length = dataStore.p[solution[0].floor][solution[0].segment].l;
							drawing[0].push(draw);
							drawing[0].routeLength = draw.length;
						} else if (dataStore.p[solution[0].floor][solution[0].segment].e[0] === startpoint) {
							draw = {};
							draw.type = 'M';
							draw.floor = solution[0].floor;
							draw.x = dataStore.p[solution[0].floor][solution[0].segment].m;
							draw.y = dataStore.p[solution[0].floor][solution[0].segment].n;
							draw.length = 0;
							drawing[0].push(draw);
							draw = {};
							draw.type = 'L';
							draw.floor = solution[0].floor;
							draw.x = dataStore.p[solution[0].floor][solution[0].segment].x;
							draw.y = dataStore.p[solution[0].floor][solution[0].segment].y;
							draw.length = dataStore.p[solution[0].floor][solution[0].segment].l;
							drawing[0].push(draw);
							drawing[0].routeLength = draw.length;
						}
	
						lastStep = 1;
	
						// for each floor that we have to deal with
						for (i = 0; i < portalsEntered + 1; i++) {
							for (stepNum = lastStep; stepNum < solution.length; stepNum++) {
								if (solution[stepNum].type === 'pa') {
									ax = dataStore.p[solution[stepNum].floor][solution[stepNum].segment].x;
									ay = dataStore.p[solution[stepNum].floor][solution[stepNum].segment].y;
									bx = dataStore.p[solution[stepNum].floor][solution[stepNum].segment].m;
									by = dataStore.p[solution[stepNum].floor][solution[stepNum].segment].n;
	
									draw = {};
									draw.floor = solution[stepNum].floor;
									if (drawing[i].slice(-1)[0].x === ax && drawing[i].slice(-1)[0].y === ay) {
										draw.x = bx;
										draw.y = by;
									} else {
										draw.x = ax;
										draw.y = ay;
									}
									draw.length = dataStore.p[solution[stepNum].floor][solution[stepNum].segment].l;
									draw.type = 'L';
									drawing[i].push(draw);
									drawing[i].routeLength += draw.length;
								}
								if (solution[stepNum].type === 'po') {
									drawing[i + 1] = [];
									drawing[i + 1].routeLength = 0;
									// push the first object on
									// check for more than just floor number here....
									pick = '';
									if (dataStore.q[solution[stepNum].segment].g === dataStore.q[solution[stepNum].segment].k) {
										if (dataStore.q[solution[stepNum].segment].x === draw.x && dataStore.q[solution[stepNum].segment].y === draw.y) {
											pick = 'B';
										} else {
											pick = 'A';
										}
									} else {
										if (dataStore.q[solution[stepNum].segment].g === solution[stepNum].floor) {
											pick = 'A';
										} else if (dataStore.q[solution[stepNum].segment].k === solution[stepNum].floor) {
											pick = 'B';
										}
									}
									if (pick === 'A') {
										draw = {};
										draw.floor = solution[stepNum].floor;
										draw.type = 'M';
										draw.x = dataStore.q[solution[stepNum].segment].x;
										draw.y = dataStore.q[solution[stepNum].segment].y;
										draw.length = 0;
										drawing[i + 1].push(draw);
										drawing[i + 1].routeLength = draw.length;
									} else if (pick === 'B') {
										draw = {};
										draw.floor = solution[stepNum].floor;
										draw.type = 'M';
										draw.x = dataStore.q[solution[stepNum].segment].m;
										draw.y = dataStore.q[solution[stepNum].segment].n;
										draw.length = 0;
										drawing[i + 1].push(draw);
										drawing[i + 1].routeLength = draw.length;
									}
									lastStep = stepNum;
									lastStep++;
									stepNum = solution.length;
								}
							}
						}
	
						//go back through the drawing and insert curves if requested
						//consolidate colinear line segments?
						if (options.path.radius > 0) {
							for (level = 0; level < drawing.length; level++) {
								for (i = 1; i < drawing[level].length - 1; i++) {
									if (drawing[level][i].type === 'L' && drawing[level][i].type === 'L') {
										// check for colinear here and remove first segment, and add its length to second
										aDX = (drawing[level][i - 1].x - drawing[level][i].x);
										aDY = (drawing[level][i - 1].y - drawing[level][i].y);
										bDX = (drawing[level][i].x - drawing[level][i + 1].x);
										bDY = (drawing[level][i].y - drawing[level][i + 1].y);
										// if the change in Y for both is Zero
										if ((aDY === 0 && bDY === 0) || (aDX === 0 && bDX === 0) || ((aDX / aDY) === (bDX / bDY) && !(aDX === 0 && aDY === 0 && bDX === 0 && bDY === 0))) {
											drawing[level][i + 1].length = drawing[level][i].length + drawing[level][i + 1].length;
	//                                      drawing[level][i+1].type = "L";
											drawing[level].splice(i, 1);
											i = 1;
										}
									}
								}
								for (i = 1; i < drawing[level].length - 1; i++) {
									// locate possible curves based on both line segments being longer than options.path.radius
									if (drawing[level][i].type === 'L' && drawing[level][i].type === 'L' && drawing[level][i].length > options.path.radius && drawing[level][i + 1].length > options.path.radius) {
										//save old end point
										cx = drawing[level][i].x;
										cy = drawing[level][i].y;
										// change x,y and change length
										px = drawing[level][i - 1].x;
										py = drawing[level][i - 1].y;
										//new=prior + ((center-prior) * ((length-radius)/length))
										drawing[level][i].x = (Number(px) + ((cx - px) * ((drawing[level][i].length - options.path.radius) / drawing[level][i].length)));
										drawing[level][i].y = (Number(py) + ((cy - py) * ((drawing[level][i].length - options.path.radius) / drawing[level][i].length)));
										//shorten current line
										drawing[level][i].length = drawing[level][i].length - options.path.radius;
										curve = {};
										//curve center is old end point
										curve.cx = cx;
										curve.cy = cy;
										//curve end point is based on next line
										nx = drawing[level][i + 1].x;
										ny = drawing[level][i + 1].y;
										curve.x = (Number(cx) + ((nx - cx) * ((options.path.radius) / drawing[level][i + 1].length)));
										curve.y = (Number(cy) + ((ny - cy) * ((options.path.radius) / drawing[level][i + 1].length)));
										//change length of next segment now that it has a new starting point
										drawing[level][i + 1].length = drawing[level][i + 1].length - options.path.radius;
										curve.type = 'Q';
										curve.floor = drawing[level][i].floor;
										// insert curve element
										// splice function on arrays allows insertion
										//   array.splice(start, delete count, value, value)
										// drawing[level].splice(current line, 0, curve element object);
	
										drawing[level].splice(i + 1, 0, curve);
	
									} // both possible segments long enough
								} // drawing segment
							} // level
						} // if we are doing curves at all
	
						$.each(drawing, function (j, map) {
							var path = '',
								newPath;
							$.each(map, function (k, stroke) {
								switch (stroke.type) {
								case 'M':
									path = 'M' + stroke.x + ',' + stroke.y;
									break;
								case 'L':
									path += 'L' + stroke.x + ',' + stroke.y;
									break;
								case 'Q':
									path += 'Q' + stroke.cx + ',' + stroke.cy + ' ' + stroke.x + ',' + stroke.y;
									break;
								}
							});
	
							newPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
							newPath.setAttribute('d', path);
							newPath.style.fill = 'none';
	
							if (newPath.classList) {
								newPath.classList.add('directionPath' + j);
							} else {
								newPath.setAttribute('class', 'directionPath' + j);
							}
	
	
							var attachPointSvg = $('#' + maps[map[0].floor].id + ' svg');
	
							attachPointSvg.append(newPath);
					
	
							thisPath = $('#' + maps[map[0].floor].id + ' svg .directionPath' + j);
	
							drawing[j].path = thisPath;
	
						});
	
						animatePath(0);
	
						//on switch which floor is displayed reset path svgStrokeDashOffset to minPath and the reanimate
						//notify animation loop?
					}
				}
			} //RouteTo

			function replaceLoadScreen(el) {
				var displayNum,
					mapNum;
	
				$('#WayfindingStatus').remove();
	
				displayNum = 0;
				for (mapNum = 0; mapNum < maps.length; mapNum++) {
					if (defaultMap === maps[mapNum].id) {
						displayNum = mapNum;
					}
				}
	
				$('#' + maps[displayNum].id, el).show();
	
				$(el).trigger('wayfinding:mapsVisible');
	
				if (typeof options.endpoint === 'function') {
					routeTo(options.endpoint(), el);
				} else if (typeof options.endpoint === 'string') {
					routeTo(options.endpoint, el);
				}
	
				$.event.trigger('wayfinding:ready');
			} 
	
			// Initialize the jQuery target object
			
			function initialize(el, cb) {
								
				var mapsProcessed = 0;
	
				$('div:not("#WayfindingStatus")', el).remove();
	
				// Load SVGs off the network
				
				$.each(maps, function (i, map) {
	
					var svgDiv = $('<div id="' + map.id + '"><\/div>');

					svgDiv.load(
						map.path,
						function (svg, status) {
							if (status === 'error') {
								svgDiv.html('<p class="text-center text-danger">Map ' + i + ' Was not found at ' +
									map.path + '<br />Please upload it in the administration section</p>');
								maps[i].el = svgDiv;
							} else {
								maps[i].svgHandle = svg;
								maps[i].el = svgDiv;
	
								cleanupSVG(maps[i].el);
	
								activateSVG(el, svgDiv);
	
								mapsProcessed += 1;
							}
	
							if(mapsProcessed === maps.length) {
								// All SVGs have finished loading
								establishDataStore(function() {
									// SVGs are loaded, dataStore is set, ready the DOM
									setStartPoint(startpoint, el);
									setOptions(el);
									replaceLoadScreen(el);
									if (typeof cb === 'function') {
										cb();
									}
								});
							}
						}
					);
				});
	
			} // function initialize
	
			if (action && typeof (action) === 'object') {
				if (typeof options === 'function') {
					callback = options;
				}
				options = action;
				passed = action;
				action = 'initialize';
			}
	
			// for each jQuery target object
			this.each(function () {
				// store reference to the currently processing jQuery object
				obj = $(this);
	
				getOptions(obj); // load the current options
	
				if (action && typeof (action) === 'string') {
					switch (action) {
					case 'initialize':
						if (passed && passed.maps) {
							initialize(obj, callback);
						} else {
							if (passed !== undefined) {
								setStartPoint(options.startpoint, obj);
							}
						}
						break;
					case 'routeTo':
						routeTo(passed, obj);
						break;
					case 'animatePath':
						hidePath(obj);
						animatePath(0);
						break;
					case 'startpoint':
						if (passed === undefined) {
							result = startpoint;
						} else {
							setStartPoint(passed, obj);
							establishDataStore(callback);
						}
						break;
					case 'currentMap':
						if (passed === undefined) {
							result = $('div:visible', obj).prop('id');
						}
						break;
					case 'path':
						if (passed === undefined) {
							result = options.path;
						} else {
							options.path = $.extend(true, {}, options.path, passed);
						}
						break;
	
					case 'getDataStore':
						result = JSON.stringify(dataStore);
						break;
					case 'destroy':
						$(obj).remove();
						break;
					default:
						break;
					}
				}

				setOptions(obj);
			});
	
			if (result !== undefined) {
				return result;
			}
	
			return this;
		};

#2

Not sure if it is a winning strategy

But if u insist I suggest to take an example other library like Chart.js or pixl-xml to see how they declare modules

Basically u need to wrap everything around a variable which u export using an export starement


#3

Indeed. I’d blame jQuery before the fear of having JS in TS. TS winds up as JS in the end anyway, so I’m not sure why it’d cause a drop in performance.