PageSourceSearch

https://dram242.be/AppeX.CoreCode.js

js dram242.be collected 2026-09-27 07:45:54 UTC 60,000 bytes, 2,010 lines download raw bytes

1// Included Classes:  JavascriptFunction JavaScriptObject JavascriptArray JavascriptNamespace ServerRequest ServerResponse EventSourceEmulator DebuggingOptions ApplicationClient
2
3
4
5function _ensureNamespace(aPathString) {	
6	//Global Utility Function
7	var eachName, 
8		aPath, 
9		currentNS;
10		aPath = aPathString.split(".");
11	//If the AppeX Application framework (plus corecode) is present, use the ensureNamespace function.
12	//This ensures backwards compatibility for classes explicitly referenced via the "$t" root namespace.
13	if (window.$t) {
14		return $t.ensureNamespace(aPathString);
15	}
16	currentNS = window;
17	while (aPath.length > 0) {
18		eachName = aPath.shift();  
19		if (!currentNS[eachName]) {
20			currentNS[eachName] = {}
21		} 
22		currentNS = currentNS[eachName];
23	}
24}
25
26
27/*****************************************************************************************
28*		AppeX.JavascriptFunction
29******************************************************************************************
30
31*****************************************************************************************/
32
33
34	Function.ClassName = "Function";
35	
36	Object.defineProperty(Function.prototype,
37		"functionName",
38		{value: function functionName() {
39			var aName,
40				aString,
41				afterKeyword;
42			aName = this.name;
43			if (aName) {
44				return aName;
45			}
46			aString = this.toString();
47  			afterKeyword = aString.substr("function ".length);
48  			aName = afterKeyword.split("(")[0].trim();
49  			return aName;
50		}
51	});
52	
53	Object.defineProperty(Function.prototype,
54		"bindMethod",
55		{value: function bindMethod(aFunction) {
56			Object.defineProperty(
57				this.prototype,
58				aFunction.functionName(),
59				{value: aFunction, enumerable: false, writable: true});
60			return aFunction;
61		}
62	});
63	
64
65//  --------------------------------------------------------------------------------------
66Function.bindMethod(function fullName() {
67		
68	var prefix = this.Namespace ? (this.Namespace.fullName() + ".") : "";
69	var className = this.ClassName ? this.ClassName : "<unknown>";
70	return prefix + className
71});
72
73
74//  --------------------------------------------------------------------------------------
75Function.bindMethod(function getNamespace() {
76
77	return this.Namespace ? this.Namespace :  {}
78});
79
80
81
82/*****************************************************************************************
83*		AppeX.JavaScriptObject
84******************************************************************************************
85
86*****************************************************************************************/
87
88	
89Object.ClassName = "Object";
90
91//  --------------------------------------------------------------------------------------
92Object.bindMethod(function callSuper(aFunctionName, arguments) {
93
94	
95	//self deprecated: #(#version '9.0' #sunset '9.1').
96
97	var superclass,
98		aFunction;
99
100	var aString = '  DEPRECATED CODE WARNING!!  "callSuper" is deprecated in AppeX.  Please rewrite this call as "super.' 
101		+ aFunctionName 
102		+ '(' 
103		+ arguments
104		+ ');"' 
105	;
106	console.warn(aString);
107	
108	superclass = this.superclass();
109	//apply the superclass implementation in the context of 'this'
110	aFunction = superclass.prototype[aFunctionName];
111	return aFunction.apply(this, arguments);
112
113});
114
115
116//  --------------------------------------------------------------------------------------
117Object.bindMethod(function eventTarget() {
118
119	// Simulate the object's ability to implement EventTarget API.
120	return this._eventTarget = this._eventTarget || document.createTextNode('eventTarget');
121});
122
123
124//  --------------------------------------------------------------------------------------
125Object.bindMethod(function getClassName() {
126
127	return this.constructor.name;
128});
129
130
131//  --------------------------------------------------------------------------------------
132Object.bindMethod(function getFullClassName() {
133
134	return $t.getFullClassName(this);
135});
136
137
138//  --------------------------------------------------------------------------------------
139Object.bindMethod(function subclassResponsibility(methodName) {
140
141	$t.debug(this, " must implement " + methodName);
142});
143
144
145//  --------------------------------------------------------------------------------------
146Object.bindMethod(function superclass() {
147
148	return Object.getPrototypeOf(Object.getPrototypeOf(this)).constructor;
149
150
151});
152
153
154//  --------------------------------------------------------------------------------------
155Object.bindMethod(function triggerEvent(type, detail){
156
157	var event;
158	if (!this.dispatchEvent) { 
159		return;
160	};
161	event = document.createEvent("CustomEvent");
162	event.initCustomEvent(type, true, true, detail);
163	this.dispatchEvent(event);
164});
165
166
167
168/*****************************************************************************************
169*		AppeX.JavascriptArray
170******************************************************************************************
171
172*****************************************************************************************/
173
174	
175	Array.ClassName = "Array";
176
177
178//  --------------------------------------------------------------------------------------
179Array.bindMethod(function eachDo(aFunction) {
180
181	for (var i = 0; i < this.length; i++) {
182		aFunction(this[i]);
183	}
184
185});
186
187
188//  --------------------------------------------------------------------------------------
189Array.bindMethod(function includesAnyOf(anArray) {
190
191	var self = this,
192		aBoolean = false;
193	anArray.eachDo(
194		function (eAnArrayElement) {
195			if (self.includes(eAnArrayElement)) { 
196				aBoolean = true;
197			}			
198		}
199	);
200	return aBoolean;
201});
202
203
204
205/*****************************************************************************************
206*		JavascriptNamespace
207******************************************************************************************
208
209*****************************************************************************************/
210(function () {
211Namespace = class JavascriptNamespace {
212//  --------------------------------------------------------------------------------------
213constructor(aName) {
214
215	this.name = aName;
216	
217}
218
219
220//  --------------------------------------------------------------------------------------
221debug() {
222
223	$t._debuggingOptions = $t._debuggingOptions || new AppeX.DebuggingOptions();
224	if (arguments.length !== 0) {
225		$t._debuggingOptions.applyOptions(null, arguments);
226	}
227	
228}
229
230
231//  --------------------------------------------------------------------------------------
232defineNamespace(aName) {
233
234	var namespace = this[aName];
235
236	if (namespace instanceof $t.Namespace) {
237		return namespace;
238	}
239	namespace = new $t.Namespace(aName);
240	this[aName] = namespace;
241	namespace.parent = this;
242	if (this === $t) {
243		//If a global namespace of some sort already exists (e.g. AppeX), don't  just delete it, copy its properties
244		//over to the newly created namespace which will take its place as the global.
245		this.replace(namespace, window[aName]);
246		window[aName] = namespace;
247	}
248	return namespace;
249}
250
251
252//  --------------------------------------------------------------------------------------
253ensureNamespace(aName) {
254
255	var path = aName.split("."),
256		 currentNamespace = this;
257
258	while (path.length > 0) {
259		var eachName = path.shift();
260		currentNamespace = currentNamespace.defineNamespace(eachName);
261	}
262	return currentNamespace;
263}
264
265
266//  --------------------------------------------------------------------------------------
267getFullClassName(anObject) {
268
269	var theName = anObject.constructor.fullName ? anObject.constructor.fullName() : anObject.constructor.name;
270	return theName.replace("$t.", "");
271	
272}
273
274
275//  --------------------------------------------------------------------------------------
276initialize() {
277
278	//Create the root "Smalltalk" Namespace "$t"
279	$t = new Namespace("Smalltalk");
280	$t.Namespace = Namespace;
281 
282}
283
284
285//  --------------------------------------------------------------------------------------
286replace(target, source) {
287
288	// replace the properties in the target with those in the source (as in extjs apply or jquery extend)
289	// return the target
290	if (!source) { 
291		return target 
292	};
293	for (var key in source) { 
294		target[key] = source[key] 
295	};
296	return target;
297	
298}
299
300
301};
302
303
304	//Create the root "Smalltalk" Namespace "$t"
305	$t = new Namespace("Smalltalk");
306	$t.Namespace = Namespace;
307 
308
309})();
310/*****************************************************************************************
311*		AppeX.ServerRequest
312******************************************************************************************
313
314*****************************************************************************************/
315$t.ensureNamespace("AppeX");
316
317AppeX.ServerRequest = class ServerRequest {
318//  --------------------------------------------------------------------------------------
319constructor(url, data, method, asynchronous) {
320	// Initialize the receiver. Make sure I have valid data and method.
321	// Set up a deliveryMap to map between HTTP methods and functions
322	// that should be invoked when asked to deliver the data.
323	// Set up a container object for callbacks.
324	this.url = url;
325	this.data = data || {};
326	this.method = method || "POST";
327	this.method = this.method.toUpperCase();
328	this.asynchronous = !(asynchronous === false);
329	this._deliveryMap = {
330		"GET" : this.doGetData,
331		"POST" : this.doPostData,
332		"PUT" : this.doPutData,
333		"DELETE" : this.doDeleteData
334	};
335	this._callbacks = {};
336	return this;
337}
338
339
340//  --------------------------------------------------------------------------------------
341deliver() {
342	// Deliver the request to the server.
343	// Find which one of the delivery functions should be invoked.
344	var deliveryFunction = this._deliveryMap[this.method];
345	this.httpRequest = new XMLHttpRequest();
346	this.response = new $t.AppeX.ServerResponse(this);
347	deliveryFunction.apply(this, [this.url, this.data]);
348	return this.response;
349}
350
351
352//  --------------------------------------------------------------------------------------
353doDeleteData(url, data) {
354	// This is the 'DELETE' delivery function.
355	// Encode my data as a query string to include as part of the URL.
356	var request = this.httpRequest;
357	request.open("DELETE", url + "?" + this.encodeFormData(data), this.asynchronous);
358	if (this.asynchronous) {
359		this.setupCallbacks(request);
360	}
361	this.sendRequest(request, null);
362	if (!this.asynchronous) {
363		this.request_DONE(request);
364	}
365}
366
367
368//  --------------------------------------------------------------------------------------
369doGetData(url, data) {
370	// This is the 'GET' delivery function.
371	// Encode my data as a query string to include as part of the URL.
372	var request = this.httpRequest,
373		query = this.encodeFormData(data);
374	if (query.length > 0) {
375		query = "?" + query;
376	}
377	request.open("GET", url + query, this.asynchronous);
378	if (this.asynchronous) {
379		this.setupCallbacks(request);
380	}
381	this.sendRequest(request, null);
382	if (!this.asynchronous) {
383		this.request_DONE(request);
384	}
385}
386
387
388//  --------------------------------------------------------------------------------------
389doPostData(url, data) {
390	// This is the 'POST' delivery function.
391	// data is sent as x-www-form-urlencoded.
392	var request = this.httpRequest;
393	request.open("POST", url, this.asynchronous);
394	if (this.asynchronous) {
395		this.setupCallbacks(request);
396	}
397	request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
398	this.sendRequest(request, this.encodeFormData(data));
399	if (!this.asynchronous) {
400		this.request_DONE(request);
401	}
402}
403
404
405//  --------------------------------------------------------------------------------------
406doPutData(url, data) {
407	// This is the 'PUT' delivery function.
408	// data is sent as x-www-form-urlencoded.
409	var request = this.httpRequest;
410	request.open("PUT", url, this.asynchronous);
411	if (this.asynchronous) {
412		this.setupCallbacks(request);
413	}
414	request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
415	this.sendRequest(request, this.encodeFormData(data));
416	if (!this.asynchronous) {
417		this.request_DONE(request);
418	}
419}
420
421
422//  --------------------------------------------------------------------------------------
423encodeFormData(data) {
424	// Encode the properties of an object as if thery were name/value pairs from
425	// an HTML form, using application/x-www-form-urlencoded format.
426	if (!data) return "";
427	var pairs = [];
428	$t.debug("Encoding Form Data:", data);
429	for (var name in data) {
430		if (!data.hasOwnProperty(name)) continue;
431		if (typeof data[name] === "function") continue;
432		var value = data[name].toString();
433		name = encodeURIComponent(name).replace("%20", "+");
434		value = encodeURIComponent(value).replace("%20", "+");
435		pairs.push(name + "=" + value);
436	};
437	$t.debug("Encoded Form Data:", pairs);
438	return pairs.join("&");
439}
440
441
442//  --------------------------------------------------------------------------------------
443ensureCallback(readyState, httpStatus) {
444	var callbacks = (this._callbacks[readyState] = this._callbacks[readyState] || {});
445	return callbacks[httpStatus] = callbacks[httpStatus] || [];
446}
447
448
449//  --------------------------------------------------------------------------------------
450processCallbacks(readyState, status) {
451	var self = this;
452	// Process the higher-level callbacks that operate on my response data.
453	this.ensureCallback(readyState, status);
454	var readyStateCallbacks = self._callbacks[readyState];
455	var statusCallbacks = readyStateCallbacks[status] || [];
456	statusCallbacks.forEach(
457		function(callback) {
458			self.processReadyStateChange(callback);
459		}
460	)
461}
462
463
464//  --------------------------------------------------------------------------------------
465processReadyStateChange(callback) {
466	// At this point, all low level callback processing is done by me.
467	// I let the registered callbacks operate on my response and its data.
468	// It is assumed that callback knows for which readyState/status it has been registered
469	// because I don't care any more what these are.
470	$t.debug(this, "Processing response CALLBACK:", callback);
471	callback(this.response);	
472}
473
474
475//  --------------------------------------------------------------------------------------
476request_DONE(httpRequest) {
477	// XMLHttpRequest.DONE has been detected.
478	$t.debug("XMLHttpRequest.DONE. Creating ServerResponse object from XMLHttpRequest:", httpRequest);
479	var response = this.response; /* a ServerResponse */
480	response.isReceiving = true;
481	response.httpStatus = httpRequest.status;
482	response.contentType = httpRequest.getResponseHeader("Content-Type");
483	response.data = httpRequest.response;
484	response.isReceiving = false;
485	if (httpRequest.status === 200) {
486		response.isSuccess = true;
487	} else if (httpRequest.status >= 400) {
488		response.isError = true;
489		response.data = {
490			status: httpRequest.status,
491			statusText: httpRequest.statusText,
492			response: httpRequest.response
493		}
494	}
495	response.processData();
496	response.isDone = true;
497	response.processCallbacks();
498}
499
500
501//  --------------------------------------------------------------------------------------
502request_HEADERS_RECEIVED(httpRequest) {
503	// Nothing special to do here. Placeholder for future enhancements.
504}
505
506
507//  --------------------------------------------------------------------------------------
508request_LOADING(httpRequest) {
509	// Nothing special to do here. Placeholder for future enhancements.
510}
511
512
513//  --------------------------------------------------------------------------------------
514request_OPENED(httpRequest) {
515	// Nothing special to do here. Placeholder for future enhancements.
516}
517
518
519//  --------------------------------------------------------------------------------------
520request_UNSENT(httpRequest) {
521	// Nothing special to do here. Placeholder for future enhancements.
522}
523
524
525//  --------------------------------------------------------------------------------------
526sendRequest(httpRequest, data) {
527	$t.debug("Sending XMLHttpRequest:", httpRequest, data);
528	httpRequest.send(data);
529}
530
531
532//  --------------------------------------------------------------------------------------
533setupCallbacks(httpRequest) {
534	// save the current context to use inside the callback function.
535	var self = this;	
536	httpRequest.onreadystatechange = function() {
537		// First, process the Http request and set some internal values from it.
538		$t.debug("Processing XMLHttpRequest.readyState:", httpRequest.readyState);
539		switch(httpRequest.readyState) {
540			//A commonly used JS library is known to make the properties XMLHttpRequest.UNSENT etc. unavailabe.
541			//Therefore if necessary default to readyState values documented at https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/readyState	
542			case XMLHttpRequest.UNSENT? XMLHttpRequest.UNSENT : 0:	
543				self.request_UNSENT(httpRequest);
544				break;			
545			case XMLHttpRequest.OPENED? XMLHttpRequest.OPENED : 1:	
546				self.request_OPENED(httpRequest);
547				break;		
548			case XMLHttpRequest.HEADERS_RECEIVED? XMLHttpRequest.HEADERS_RECEIVED : 2:
549				self.request_HEADERS_RECEIVED(httpRequest);
550				break;
551			case XMLHttpRequest.LOADING ? XMLHttpRequest.LOADING : 3:
552				self.request_LOADING(httpRequest);
553				break;		
554			case XMLHttpRequest.DONE? XMLHttpRequest.DONE : 4:
555				self.request_DONE(httpRequest);
556				break;
557		};
558
559		// Next, process the higher-level callbacks that operate on my response data.
560		self.processCallbacks(httpRequest.readyState, httpRequest.status);
561	};
562
563}
564
565
566};
567
568/*****************************************************************************************
569*		AppeX.ServerResponse
570******************************************************************************************
571
572*****************************************************************************************/
573$t.ensureNamespace("AppeX");
574
575AppeX.ServerResponse = class ServerResponse {
576//  --------------------------------------------------------------------------------------
577addContentHandler(rule, handler) {
578	// Add the rule/handler pair to my content handlers. This would allow cusome content handling
579	// beyond what the standard handlers will be.
580	// @rule:
581	//	a function that will receive my contentType in the argument and will return a boolean
582	// 	indicating whether the rule is a match.
583	// @handler:
584	//	a function that will be applied to myself when @rule returns true.
585	this._contentHandlers.push([rule, handler]);
586}
587
588
589//  --------------------------------------------------------------------------------------
590constructor(aRequest) {
591
592	this.object = null;
593	this.request = aRequest;
594	this.callbacks = {
595		"onSuccess": function() {},
596		"onError": function(error) {
597			console.error(error, aRequest);
598		}
599	};
600	this.initializeContentHandlers();
601	this.setupDefaultContentHandlers();
602}
603
604
605//  --------------------------------------------------------------------------------------
606initializeContentHandlers() {
607	this._contentHandlers = [];
608	return this;
609}
610
611
612//  --------------------------------------------------------------------------------------
613onError(callbackFunction) {
614	// if I am not done, push callbackFunction on the error callbacks queue.
615	// Otherwise, process callbackFunction immediately if I am in error.
616	// Always return myself to allow method chaining.
617	if (!this.isDone) {
618			this.callbacks.onError = callbackFunction;
619			return this;
620	} else if (this.isError) {
621		this.processCallbacks(callbackFunction);
622	}
623	return this;
624}
625
626
627//  --------------------------------------------------------------------------------------
628onSuccess(callbackFunction) {
629	// if I am not done, push callbackFunction on the sucess callbacks queue.
630	// Otherwise, process callbackFunction immediately if isSuccess.
631	// Always return myself to allow method chaining.
632	if (!this.isDone) {
633			this.callbacks.onSuccess = callbackFunction;
634			return this;
635	} else if (this.isSuccess) {
636		this.processCallbacks(callbackFunction);
637	}
638	return this;
639}
640
641
642//  --------------------------------------------------------------------------------------
643processCallbacks(callback) {
644	// depending on the status, process the right callback.
645	if (! this.isDone) {
646		throw new Error("The processCallbacks function must not be called before the response is done.");
647		return;
648	}
649	// if callback is defined, evaluate it.
650	// if callback is not defined, find which callback should be executed and re-enter this function with
651	// the proper callback in argument.
652	if (callback !== undefined) {
653		return callback(this.object);
654	}
655	
656	// execute the generic version
657	if (this.isSuccess) {
658		return this.callbacks.onSuccess(this.object);
659	}
660	if (this.isError) {
661		return this.callbacks.onError(this.object);
662	}
663}
664
665
666//  --------------------------------------------------------------------------------------
667processData() {
668	var i,
669	handlers,
670	pair,
671	match;
672	if (this.data === null) {
673		return this;
674	}
675	handlers = this._contentHandlers;
676	for (i = 0;  i< handlers.length;  i++) {
677		pair = handlers[i];
678		match = pair[0].call(this,this.contentType);
679		if (match) {
680			pair[1].call(this, this);
681			break;
682		}
683	}
684	//cannot just check 'if (this.object)', since legitimate json responses such as 0 and false are both falsey and would be replaced.
685	if (this.object === null) {
686		this.object = this.data;
687	}
688	this.isDone = true;
689	return this;
690}
691
692
693//  --------------------------------------------------------------------------------------
694setupDefaultContentHandlers() {
695	// Define the default content handlers.
696	// If an application wants to use custom content handlers for a response,
697	// it should call <response>.initializeContentHandlers()
698	// followed by a sequence of <response>.addContentHandler(rule, handler) calls to define its own.
699	// the rules are evaluated on a 'first win' basis, so the order is important here.
700	// For example, if a catch-all rule for text is used but 'text/html' is handled differently,
701	// the rule for 'text/html' must come before 'text/*'.
702
703	this.addContentHandler(
704		function(contentType) {
705			return contentType == undefined
706		},
707		function(response) {
708			response.object = response.data;
709			return response;
710		}
711	);
712	this.addContentHandler(
713		function(contentType) {
714			return contentType.match(/^text/);
715		},
716		function(response) {
717			response.object = response.data;
718			return response;
719		}
720	);
721	this.addContentHandler(
722		function(contentType) {
723			return contentType == "application/json";
724		},
725		function(response) {
726			response.object = JSON.parse(response.data);
727			return response;
728		}
729	);
730	this.addContentHandler(
731		function(contentType) {
732			return true;
733		},
734		function(response) {
735			response.object = response.data;
736			return response;
737		}
738	);
739
740	return this;
741}
742
743
744};
745
746/*****************************************************************************************
747*		AppeX.EventSourceEmulator
748******************************************************************************************
749
750*****************************************************************************************/
751$t.ensureNamespace("AppeX");
752
753AppeX.EventSourceEmulator = class EventSourceEmulator {
754//  --------------------------------------------------------------------------------------
755addEventListener(type, listener, useCapture) {
756// Simulate the EventTarget API
757
758	return this.eventTarget().addEventListener(type, listener, useCapture || false);
759}
760
761
762//  --------------------------------------------------------------------------------------
763close() {
764	this.aborted = true;
765	this.xhr.abort();
766	this.readyState = EventSource.CLOSED;
767}
768
769
770//  --------------------------------------------------------------------------------------
771connect() {
772	// This is how we establish a connection
773	this.type = null;
774	this.xhr.open("GET", this.url);
775	this.xhr.setRequestHeader("Cache-Control", "no-cache");
776	if (this.lastEventId) {
777		this.xhr.setRequestHeader("Last-Event-ID", this.lastEventId);
778	}
779	this.xhr.send();
780}
781
782
783//  --------------------------------------------------------------------------------------
784constructor(url) {
785
786	// Initialize newly created object.
787	this.type = null; 				// To check property response type.
788	this.lastEventId = ""; 			// For resyncing with the server
789	this.retryDelay = 3000; 		// Delay between connection attempts
790	this.aborted = false;			// Set true to give up on connecting
791
792	Object.defineProperty(
793		this,
794		"url",
795		{ value: url, writable: false, enumerable: true, configurable: false }
796	);
797
798	// Store this for callbacks:
799	var self = this;
800
801	// Create an XHR object
802	var xhr = this.xhr = new XMLHttpRequest(); 
803	this.readyState = EventSource.CONNECTING;	// Initial state,
804
805	// Define an event handler for it
806	xhr.onreadystatechange = function() {
807		switch(xhr.readyState) {
808			// It is safe to use EventSource. We only run this code if EventSource exists - i.e., this.constructor.
809			// When the request opens (XHR.OPENED)
810			case 1:
811				self.readyState = EventSource.OPEN;
812				self.charsReceived = 0; //for reconnect after readystate done 
813				break;
814			// When a chunk of data arrives (XHR.LOADING)
815			case 3:
816				self.processData();
817				break;
818			// When the request closes (XHR.DONE)						
819			case 4:
820				self.readyState = EventSource.CONNECTING;
821				self.reconnect();
822				break;
823		}
824	};
825
826	// And after a slight delay to allow things settle down, establish a long-lived connection through it
827	setTimeout(function() { self.connect() }, 300 /* milliseconds */ );
828}
829
830
831//  --------------------------------------------------------------------------------------
832createEvent(eventType, data) {
833// Create a new MessageEvent object for eventType and data.
834
835	var event = document.createEvent("MessageEvent");
836	var origin = this.url;
837	var source = this;
838	var bubbles = false;
839	var cancelable = false;
840	
841	event.initMessageEvent(
842		eventType,
843		bubbles,
844		cancelable,
845		data,
846		origin,
847		this.lastEventId,
848		source);
849
850	return event;
851}
852
853
854//  --------------------------------------------------------------------------------------
855dispatchEvent(event) {
856// Simulate the EventTarget API
857
858	if (! this._eventTarget) { return null };
859	return this._eventTarget.dispatchEvent(event);
860}
861
862
863//  --------------------------------------------------------------------------------------
864processData() {
865// Each time data arrives, process it and trigger the onmessage handler
866// This function handles the details of the Server-Sent Events protocol
867
868	if (!this.type) {
869		// Check the response type if we haven"t already
870		this.type = this.xhr.getResponseHeader("Content-Type");
871		if (this.type !== "text/event-stream") {
872			return this.close();
873		}
874	}
875
876	var eventName = "message";		// The default type field of our event objects
877	var data = "";					// Holds message data
878
879	// Keep track of how much we"ve received and get only the
880	// portion of the response that we haven"t already processed.
881	var chunk = this.xhr.responseText.substring(this.charsReceived);
882	this.charsReceived = this.xhr.responseText.length;
883
884	// Break the chunk of text into lines and iterate over them.
885	var lines = chunk.replace(/(\r\n|\r|\n)$/, "").split(/\r\n|\r|\n/);
886
887	// Read lines and process the keys from the data stream.
888	for(var i = 0; i < lines.length; i++) {
889		var line = lines[i];
890		var pos = line.indexOf(":");
891		var name;
892		var value="";
893		if (pos == 0) continue;	// Ignore comments
894		if (pos > 0) {				// field name:value
895			name = line.substring(0,pos);
896			value = line.substring(pos+1);
897			if (value.charAt(0) == " ") {
898				value = value.substring(1);
899			}
900		} else {
901			name = line;			// field name only
902		}
903
904		switch(name) {
905			case "event":		eventName = value; break;
906			case "data":		data += value + "\n"; break;
907			case "id":		this.lastEventId = value; break;
908			case "retry":		this.retryDelay = parseInt(value) || 3000; break; 
909			default: break;  	// Ignore any other line
910		}
911
912		// A blank line means send the event
913		if (line === "") {
914			// If no data, try next. 
915			if (data === "") continue;
916
917			// Chop trailing newline if there is one
918			if (data.charAt(data.length-1) == "\n") {
919				data = data.substring(0, data.length-1);
920			};
921
922			// Create a new event and dispatch it.
923			var event = this.createEvent(eventName, data);
924			eventName == 'message' && this.onmessage && this.onmessage(event);
925			this.dispatchEvent(event);
926			data = "";
927			continue;
928		}
929	}
930}
931
932
933//  --------------------------------------------------------------------------------------
934reconnect() {
935// If the connection closes normally, wait a second and try to restart
936	if (this.aborted) return;				// Don't reconnect after an abort
937	if (this.xhr.status >= 300) return;	// Don't reconnect after an error
938
939	// Wait a bit, then reconnect
940	var self = this;
941	setTimeout(
942		function() { 
943			self.connect() 
944		},
945		this.retryDelay
946	);
947}
948
949
950//  --------------------------------------------------------------------------------------
951removeEventListener(type, listener, useCapture) {
952// Simulate the EventTarget API
953
954	if (! this._eventTarget) { return null };
955	return this._eventTarget.removeEventListener(type, listener, useCapture || false)
956}
957
958
959};
960
961
962	$t.AppeX.EventSourceEmulator.CONNECTING = 0;
963	$t.AppeX.EventSourceEmulator.OPEN = 1;
964	$t.AppeX.EventSourceEmulator.CLOSED = 2;
965
966
967// Emulate the EventSource API for browsers that do not support it.
968// Requires an XMLHttpRequest that sends readystatechange events whenever
969// there is new data written to a long-lived HTTP connection. Note that
970// this is not a complete implementation of the API: it does not support the
971// readyState property, the close() method, nor the open and error events.
972// Also event registration for message events is through the onmessage 
973// property only--this version does not define an addEventListener method.
974	if (window.EventSource === undefined) {
975		// If EventSource is not defined,
976		window.EventSource = $t.AppeX.EventSourceEmulator;
977	}
978
979
980/*****************************************************************************************
981*		AppeX.DebuggingOptions
982******************************************************************************************
983
984*****************************************************************************************/
985$t.ensureNamespace("AppeX");
986
987AppeX.DebuggingOptions = class DebuggingOptions {
988//  --------------------------------------------------------------------------------------
989applyOptions(object, arrayOfArguments) {
990// Use my options to notify the user with the arguments passed.
991	var label;
992	if(object) {
993		label = object.getClassName();
994	}
995	else { 
996		label = "AppeX System"
997	}
998	if (this.log) {
999		console.log("%c %s Log ", "color:darkBlue; background:lightGrey; font-size: larger", label);
1000		if(object) {
1001			console.log(object);
1002		}
1003		for (var i = 0; i < arrayOfArguments.length; i++) {
1004			var argument = arrayOfArguments[i];
1005			if (typeof argument === 'object') {
1006				console.dir(argument);
1007			} else {
1008				console.log(argument)
1009			}
1010		}
1011	}
1012	
1013	if (this.trace) {
1014		console.log("%c %s Trace ", "color:red; background:lightGrey; font-size: larger", label);
1015		if(object) {
1016			console.log(object);
1017		}
1018		if (console.trace) { 
1019			console.trace.apply(console, arrayOfArguments);
1020		} else {
1021			console.log.apply(console, arrayOfArguments);
1022		}
1023	}
1024
1025	if (this.debugger) {
1026		debugger;
1027	}
1028	
1029}
1030
1031
1032//  --------------------------------------------------------------------------------------
1033constructor() {
1034
1035	this.off();
1036}
1037
1038
1039//  --------------------------------------------------------------------------------------
1040off() {
1041	this.log = false;
1042	this.trace = false;
1043	this.debugger = false;
1044}
1045
1046
1047//  --------------------------------------------------------------------------------------
1048option(option, boolean) {
1049
1050	// Set or get a debugging option
1051	var	index,
1052		levels = ["log", "trace", "debugger"];
1053	if (boolean === undefined) {
1054		return this[option];
1055	}
1056	index = levels.indexOf(option);
1057	if (index != -1) {
1058		this[option] = boolean;
1059	}
1060	return this[option];
1061}
1062
1063
1064};
1065
1066/*****************************************************************************************
1067*		AppeX.ApplicationClient
1068******************************************************************************************
1069
1070*****************************************************************************************/
1071$t.ensureNamespace("AppeX");
1072
1073AppeX.ApplicationClient = class ApplicationClient {
1074//  --------------------------------------------------------------------------------------
1075_debugger(boolean) {
1076	return this._debugging("debugger", boolean);
1077}
1078
1079
1080//  --------------------------------------------------------------------------------------
1081_debugging(option, boolean) {
1082// Get or set a debugging option named @option.
1083// If boolean is undefined, it's a 'get', otherwise 'set'.
1084// If boolean is undefined and I haven't set my debugging options, the return value will be false.
1085	if (boolean === undefined) {
1086		if (!this._debuggingOptions) {
1087			return false;
1088		}
1089	}
1090	this._debuggingOn();
1091	return this._debuggingOptions.option(option, boolean);
1092}
1093
1094
1095//  --------------------------------------------------------------------------------------
1096_debuggingOff() {
1097	this._debuggingOptions && this._debuggingOptions.off();
1098}
1099
1100
1101//  --------------------------------------------------------------------------------------
1102_debuggingOn() {
1103	this._debuggingOptions = this._debuggingOptions || new AppeX.DebuggingOptions();
1104}
1105
1106
1107//  --------------------------------------------------------------------------------------
1108_log(boolean) {
1109	return this._debugging("log", boolean);
1110}
1111
1112
1113//  --------------------------------------------------------------------------------------
1114_setLanguage(aString, aCallback) {
1115// aString is the code of the language to be set, e.g., 'en'
1116// aCallback is an optional anonymous function to be executed after the language has been successfully set.
1117
1118	var self = this,
1119		aCatalogFromStorage,
1120		response;
1121	
1122	//first check if the catalog is already present in memory
1123	if ($t._catalogs[aString]) {
1124		this._language = aString;
1125		//put the name of the current language in sesssion storage
1126		this.storeItemInStorage('appex_language', this._language, window.sessionStorage);	
1127		aCallback && aCallback();
1128		return;
1129	} else {
1130		//if the catalog for the language is not in memory, try to retrieve the data from session storage
1131		aCatalogFromStorage = this.getItemFromStorage('appex_' + aString, window.sessionStorage);
1132		if (aCatalogFromStorage) {
1133			this._language = aString;
1134			//cache the catalog data retrieved from storage in memory
1135			$t._catalogs[this._language] = aCatalogFromStorage;
1136			//put the name of the current language in sesssion and local storage
1137			this.storeItemInStorage('appex_language', this._language, window.sessionStorage);	
1138			this.storeItemInStorage('appex_language', this._language, window.localStorage);	
1139			aCallback && aCallback();
1140			return;
1141		}
1142	}
1143	
1144	response = this.asyncMessageToServer("_appex_catalogForLanguage", aString)
1145		.onError( function (error) { console.log("Error:", error) })
1146		.onSuccess( function (data) { 
1147			self._language = aString;
1148			$t._catalogs[self._language] = data;
1149			//put the current language in session and local storage
1150			self.storeItemInStorage('appex_language', self._language, window.sessionStorage);
1151			self.storeItemInStorage('appex_language', self._language, window.localStorage);
1152			//put the corresponding catalog in session storage	
1153			self.storeItemInStorage('appex_' + self._language, data, window.sessionStorage);			
1154			aCallback && aCallback();
1155	 });
1156	
1157}
1158
1159
1160//  --------------------------------------------------------------------------------------
1161_trace(boolean) {
1162	return this._debugging("trace", boolean);
1163}
1164
1165
1166//  --------------------------------------------------------------------------------------
1167_translate(key, defaultString, catalogId) {
1168
1169	//The only required parameter is 'key'.
1170	//If there is a catalog value for 'key', then that value will be returned.
1171	//If there is no catalog value for 'key', but a 'defaultString' is supplied, the 'defaultValue' will be returned.
1172	//If there is no catalog entry for 'key', and no 'defaultString' is supplied, then the 'key' itself will be returned.
1173	//If there is no catalog, then the key will be returned.
1174
1175	//In summary, one of the following three possible values will be returned, in order of preference:
1176	//1. value looked up in catalog 2.supplied default string 3.key.
1177	
1178
1179	var aCatalog,
1180		aLocale;
1181	
1182	aLocale = this._language || 'en';
1183
1184	//in first cut ignore catalogId
1185	aCatalog =  $t._catalogs[aLocale];
1186	if (!aCatalog) { 
1187		console.log('There is no user messages catalog present for locale ' + '"' + this._language + '" or for locale "' + aLocale + '".  Adding an empty catalog.' ); 
1188		$t._catalogs[aLocale] = {};
1189		return key;
1190	}
1191	return aCatalog[key] || defaultString || key;
1192	
1193}
1194
1195
1196//  --------------------------------------------------------------------------------------
1197addEventListener(type, listener, useCapture) {
1198// Simulate the EventTarget API
1199
1200	return this.eventTarget().addEventListener(type, listener, useCapture || false);
1201}
1202
1203
1204//  --------------------------------------------------------------------------------------
1205asyncMessageToServer(path, messageArguments, messageOptions) {
1206// Force the message to be sent asynchronously.
1207
1208	var	self = this,
1209		response,
1210		args,
1211		options;
1212	
1213	args = messageArguments || [];
1214	options = messageOptions || {};
1215
1216	options.async = true;
1217
1218	response = this.messageToServer(path, args, options);
1219	response.onError(function(error) {
1220		self.errorAsyncRequestFailed(path, error);
1221	});
1222	
1223	return response;
1224}
1225
1226
1227//  --------------------------------------------------------------------------------------
1228buildHtml() {
1229// The main function for building the HTML.
1230// Do nothing by default.
1231// Subclasses will want to override.
1232
1233	return this;
1234}
1235
1236
1237//  --------------------------------------------------------------------------------------
1238cancelInterestInAllEvents()  {
1239// Cancel interest in all previously registered events.
1240
1241	if (!this.serverEvents) {
1242		return this;
1243	}
1244	
1245	var
1246		i,
1247		eventNames = Object.keys(this.serverEvents);
1248	for (var eachEvent in eventNames) {
1249		this.cancelInterestInEvent(eventNames[eachEvent]);
1250	};
1251
1252	return this;	
1253}
1254
1255
1256//  --------------------------------------------------------------------------------------
1257constructor(options){
1258
1259	var anObject = this;
1260
1261	// Keep my url so I know where to send my message.
1262	// Set the options that may have been passed to the constructor function,
1263	if (document.baseURI) {
1264		anObject.url = document.baseURI; 
1265	} else {
1266		//the full baseURI would be as below for IE
1267		anObject.url = document.location.protocol + "//" + document.location.host + document.location.pathname + '/';
1268	}
1269	anObject.queryParameters = anObject.getQueryParameters();
1270	anObject.hashPath = anObject.getHashPath();
1271	$t.replace(anObject, options);
1272	return anObject;
1273	
1274	
1275}
1276
1277
1278//  --------------------------------------------------------------------------------------
1279cancelInterestInEvent(eventName)  {
1280// If I haven't done that before, I will post a client event notifying server
1281// that I am no longer interested in @eventName.
1282// Then I remove @eventName from my serverEvents.
1283	var self = this;
1284	if (this.serverEvents && this.serverEvents[eventName]) {
1285		this.postClientEvent("cancelInterestInEvent", eventName);
1286		this.serverEvents[eventName].forEach(function(listener){
1287			self.eventChannel.source.removeEventListener (eventName,listener)
1288		});
1289		delete this.serverEvents[eventName]
1290	}
1291}
1292
1293
1294//  --------------------------------------------------------------------------------------
1295closeEventChannel() {
1296
1297	$t.debug("Closing Event Channel");
1298	this.eventChannel.source.close();
1299	this.cancelInterestInAllEvents();
1300	delete this.eventChannel.source;
1301	delete this.eventChannel;
1302	delete this.serverEvents;
1303	return this;
1304}
1305
1306
1307//  --------------------------------------------------------------------------------------
1308createEventChannel() {
1309// Create the eventChannel object to which a source will be attached later.
1310	this.eventChannel = {
1311		postUrl: "_appex_postClientEvent"
1312	};
1313
1314	return this;
1315}
1316
1317
1318//  --------------------------------------------------------------------------------------
1319createEventSource(path) {
1320// Create a new EventSource with @url as a base and my sessionId in the query string.
1321	return new EventSource(this.url + path + "?" + this.sessionQuery());
1322}
1323
1324
1325//  --------------------------------------------------------------------------------------
1326debug() {
1327// If I have set up debugging options, use them to do logging, debugging, etc.
1328	var options = this._debuggingOptions;
1329	if (options) {
1330		options.applyOptions(this, arguments);
1331	}
1332}
1333
1334
1335//  --------------------------------------------------------------------------------------
1336DELETE(path, parameters) {
1337// 'delete' is a Javascript reserved word. I have to be named with capital letters.
1338	return this.asyncMessageToServer(
1339		path,
1340		parameters,
1341		{ method: 'DELETE' }
1342	);
1343}
1344
1345
1346//  --------------------------------------------------------------------------------------
1347dispatchEvent(event) {
1348// Simulate the EventTarget API
1349
1350	if (! this._eventTarget) { return null };
1351	return this._eventTarget.dispatchEvent(event);
1352}
1353
1354
1355//  --------------------------------------------------------------------------------------
1356errorAsyncRequestFailed(path, error) {
1357	$t.debug("Request Failed. Response:", path, error.response);
1358}
1359
1360
1361//  --------------------------------------------------------------------------------------
1362GET(path, parameters) {
1363	
1364	return this.asyncMessageToServer(
1365		path,
1366		parameters,
1367		{ method: 'GET' }
1368	);
1369}
1370
1371
1372//  --------------------------------------------------------------------------------------
1373getHashPath() {
1374// Get the current document location hash string and parse it into an Array that represents the hash.
1375
1376	var	hash = document.location.hash,
1377		components;
1378	
1379	if(hash.length === 0) {
1380		return [];
1381	}
1382	
1383	hash = hash.split("#");
1384	// Ignore all but the last. The hash string must have only a single hash sign, and we are only interested in what comes after it.
1385	// e.g., "#list/someValue" -> ["", "list/someValue" ]
1386	hash = hash[hash.length - 1];
1387	// Split the hash into path components, and return it.
1388	// e.g., ["list", "someValue"]
1389	components = hash.split("/");
1390	for (var i = 0; i < components.length; i++) {
1391		components[i] = decodeURIComponent(components[i]);
1392	}
1393	return components;
1394}
1395
1396
1397//  --------------------------------------------------------------------------------------
1398getItemFromStorage(key, storage) {
1399	// Get an item under the @key from the browser @storage.
1400	// @storage can be sessionStorage or localStorage.
1401	// return an Object or null
1402	var stringInStorage,
1403		returnString;
1404	 stringInStorage = storage.getItem(key);
1405	//do not throw an error and halt execution trying to parse a non-json string such as 'hi' 
1406	if (this.isJSONString(stringInStorage)) {
1407		return JSON.parse(stringInStorage);
1408	}
1409	return stringInStorage
1410}
1411
1412
1413//  --------------------------------------------------------------------------------------
1414getQueryParameters() {
1415// Get the current document location search string and parse it into an object with keys and values.
1416
1417	var	search = document.location.search,
1418		query,
1419		pairs,
1420		eachPair,
1421		eachKey,
1422		eachValue,
1423		parameters = {};
1424	
1425	if(search.length === 0) {
1426		return parameters;
1427	}
1428	
1429	query = search.split("?");
1430	// Ignore all but the last. The query string must have only a single questionmark, and we are only interested in what comes after it.
1431	// e.g., "?id=123&key=value" -> ["", "id=123&key=value" ]
1432	query = query[query.length - 1];
1433	// Split the query into key/value pairs.
1434	// e.g., ["id=123", "key=value"]
1435	pairs = query.split("&");
1436	
1437	for (var i = 0; i < pairs.length; i++) {
1438		// Split each pair into key and value.
1439		// e.g., ["id", "123"]
1440		eachPair = pairs[i].split("=");
1441		eachKey = decodeURIComponent(eachPair[0]);
1442		eachValue = decodeURIComponent(eachPair[1]);
1443		parameters[eachKey] = eachValue;
1444	}
1445	return parameters;
1446}
1447
1448
1449//  --------------------------------------------------------------------------------------
1450getSessionFromStorage() {
1451	var storedSession = this.getItemFromStorage("appex_session", window.sessionStorage);
1452	$t.debug("Session item:", storedSession);
1453	var session = {};
1454	if (storedSession) {
1455		$t.replace(session, storedSession);
1456	}
1457	return session;
1458}
1459
1460
1461//  --------------------------------------------------------------------------------------
1462handleEventSourceError(eventSource,event) {
1463// An error occurred on @eventSource. A likely cause would be a network error or server shutdown.
1464// By default, just log it to the console.
1465	console.log("EventSource error occurred: ", eventSource, event);
1466	return this;
1467}
1468
1469
1470//  --------------------------------------------------------------------------------------
1471handleServerMessage(messageEvent) {
1472	// Process a generic server message. No event: has been specified in the response.
1473	// I do nothing but subclasses may override.
1474	$t.debug("Server message event:", messageEvent);
1475}
1476
1477
1478//  --------------------------------------------------------------------------------------
1479handleServerShutdown(duration) {
1480	// this is a default server shutdown handler.
1481	this.closeEventChannel();			
1482	alert ("Server is shutting down in "+ duration + " seconds");
1483	return this
1484}
1485
1486
1487//  --------------------------------------------------------------------------------------
1488handleSessionExpired() {
1489	// this is a default 'session expired' handler.
1490	// I am no longer interested in 'SessionExpired' event (no point).
1491	// If I establish a new session, I have to re-register interest again.
1492	this.cancelInterestInEvent('SessionExpired');
1493	this.closeEventChannel();
1494	this.removeItemFromStorage("appex_session", sessionStorage);	
1495	this.showSessionExpired(this.session);
1496	return this
1497}
1498
1499
1500//  --------------------------------------------------------------------------------------
1501handleSessionNotFound(data) {
1502	alert("Session not found: " + data);
1503	this.closeEventChannel();
1504}
1505
1506
1507//  --------------------------------------------------------------------------------------
1508handleSessionReady() {
1509	this.cancelInterestInEvent("SessionReady");
1510	this.buildHtml();
1511}
1512
1513
1514//  --------------------------------------------------------------------------------------
1515initialize(options) {
1516// Keep my url so I know where to send my message.
1517// Set the options that may have been passed to the constructor function,
1518	if (document.baseURI) {
1519		this.url = document.baseURI; 
1520	} else {
1521		//the full baseURI would be as below for IE
1522		this.url = document.location.protocol + "//" + document.location.host + document.location.pathname + '/';
1523	}
1524	this.queryParameters = this.getQueryParameters();
1525	this.hashPath = this.getHashPath();
1526	$t.replace(this, options);
1527}
1528
1529
1530//  --------------------------------------------------------------------------------------
1531initializeUrl() {
1532	// Keep my url so I know where to send my message.
1533	var elements = document.getElementsByTagName("base");
1534	var url = elements.length
1535		? elements[elements.length - 1].href
1536		: document.URL;
1537	this.url = url;
1538}
1539
1540
1541//  --------------------------------------------------------------------------------------
1542install(options) {
1543// Install the application, then process the installQueue.
1544	this.installApplication(options);
1545	this.buildHtml();
1546	this.postInstall();
1547}
1548
1549
1550//  --------------------------------------------------------------------------------------
1551installApplication(options) {
1552// Install myself as the sole application.
1553// Set whatever options I may have received.
1554	$t.application = this;
1555	this.constructor.application = this;
1556	$t.replace(this, options);
1557	return this;
1558}
1559
1560
1561//  --------------------------------------------------------------------------------------
1562installSession(options) {
1563	// If I am linking a session, I need to add session options to myself.
1564	// Typically, that would be "sessionKey" session parameter but the application may add others.
1565	// See Application class>>bodyInstallApplication for sample construction of sender
1566
1567	var	self = this,
1568		session,
1569		response;
1570		
1571	this.installApplication(options);
1572	session = this.getSessionFromStorage();
1573	response = this.linkSession(session);
1574	response
1575		.onError(function(error){
1576			console.error("Session link primitive failed", session, error)
1577		})
1578		.onSuccess(function() {
1579			$t.debug("Install Session. Server Response for _appex_linkSession:", response);
1580			self.installSessionSuccess(session, response.object);
1581			self.postInstall();
1582		});
1583	return this;
1584}
1585
1586
1587//  --------------------------------------------------------------------------------------
1588installSessionSuccess(session, data) {
1589
1590	this.updateSessionData(session, data);
1591	this.createEventChannel();
1592	this.registerServerEvents();
1593	this.openEventChannel();
1594
1595	return this;
1596}
1597
1598
1599//  --------------------------------------------------------------------------------------
1600isJSONString(aString) {
1601    try {
1602        JSON.parse(aString);
1603    } catch (error) {
1604	console.log('WARNING: Handled Error: ' + error);
1605        return false;
1606    }
1607    return true;
1608}
1609
1610
1611//  --------------------------------------------------------------------------------------
1612linkSession(session) {
1613
1614	return this.primitiveToServer("_appex_linkSession", session, {async: true});
1615
1616}
1617
1618
1619//  --------------------------------------------------------------------------------------
1620messageToServer(path, messageArguments, messageOptions) {
1621	/* Extract the callback options from messageArguments,
1622	* determine the type of message (synchrnous, asynchronous),
1623	* and invoke the function appropriate for the message.
1624	*/
1625
1626	var options = messageOptions || {};
1627	var argumentArray = messageArguments || [];
1628	var method = options.method ||  "POST";
1629	method = method.toUpperCase();
1630	var asynchronous = (options.async == true);
1631	var data = {};
1632	if (this.sessionKey) {
1633		var sessionKey = this.sessionKey;
1634		data[sessionKey] = this[sessionKey];
1635	}
1636	var jsonArgs = JSON.stringify(argumentArray);
1637	$t.debug("Sending Request Path:", path);
1638	$t.debug("Sending Request Arguments:", argumentArray, jsonArgs);
1639	if (jsonArgs != "{}" && jsonArgs != "[]") {
1640		data.arguments = jsonArgs;
1641	}
1642	$t.debug("Sending Request DATA:", data);
1643
1644	return this.serverRequest(method, path, data, asynchronous);
1645}
1646
1647
1648//  --------------------------------------------------------------------------------------
1649onServerEvent(eventName, callback) {
1650// Register interest in @eventName with the server.
1651// Add an event listener to my eventChannel source, and return the listener function.
1652
1653	var self = this,
1654		listener,
1655		addInterestInSource,
1656		sourceReadyHandler;
1657
1658	// First, register interest on the server itself:
1659	this.registerInterestInEvent(eventName);
1660
1661	// Keep track of the listener function for a removal when no longer interested.	
1662	listener = function(event) {
1663		callback(JSON.parse(event.data), event);
1664	};
1665	this.serverEvents[eventName].push(listener);
1666	
1667	if(this.eventChannel.source) {
1668		// EventSource has been set up, simply run the function.
1669		this.eventChannel.source.addEventListener (eventName,listener);
1670	} else {
1671		// Create a callback function in which I run the addInterestInSource.
1672		sourceReadyHandler = function(e) {
1673			// Don't forget to remove the event listener itself when invoked, to prevent a possibility of multiple callbacks.
1674			self.eventChannel.eventTarget().removeEventListener("EventSourceReady", sourceReadyHandler);
1675			self.eventChannel.source.addEventListener (eventName,listener);
1676			};
1677		// Now register the callback function.
1678		this.eventChannel.eventTarget().addEventListener("EventSourceReady", sourceReadyHandler)
1679	}
1680	return listener;
1681}
1682
1683
1684//  --------------------------------------------------------------------------------------
1685openEventChannel() {
1686	var self = this;
1687
1688	var eventSource = this.createEventSource( "_appex_getServerEvents" );
1689	eventSource.onmessage = function(event) {
1690		self.handleServerMessage(event);
1691	};
1692	eventSource.onopen = function(event) {
1693		console.log('!!! openEventChannel ', eventSource, event);
1694		//console.log(event);
1695	};
1696	eventSource.onerror = function(event) {
1697		self.handleEventSourceError(eventSource,event);
1698	};
1699	this.eventChannel.source = eventSource;
1700	this.eventChannel.eventTarget().triggerEvent("EventSourceReady");
1701
1702	return this;
1703}
1704
1705
1706//  --------------------------------------------------------------------------------------
1707POST(path, parameters) {
1708	return this.asyncMessageToServer(
1709		path,
1710		parameters,
1711		{ method: 'POST' }
1712	);
1713}
1714
1715
1716//  --------------------------------------------------------------------------------------
1717postClientEvent(eventName, data) {
1718// Post event with @eventName to the server.
1719// This allows the server to notify its other clients about
1720// the event having occurred here.
1721
1722	var self = this,
1723		response;
1724	response =  this.asyncMessageToServer(
1725		this.eventChannel.postUrl,
1726		{
1727			eventName: eventName,
1728			data: data
1729		}
1730	)
1731	.onError(function (error) {
1732		self.postClientEventError(error);
1733	})
1734	.onSuccess(function () {
1735		self.postClientEventSuccess(response);
1736	});
1737	return response;
1738}
1739
1740
1741//  --------------------------------------------------------------------------------------
1742postClientEventError(error) {
1743
1744	$t.debug('Log error by default.  Override postClientEventError in subclasses. ' + error.response);
1745}
1746
1747
1748//  --------------------------------------------------------------------------------------
1749postClientEventSuccess(response) {
1750	//by default do nothing.  Override in subclasses
1751	$t.debug("Client Event Success:", response);
1752}
1753
1754
1755//  --------------------------------------------------------------------------------------
1756postInstall() {
1757// Hook to do post-install actions. Do nothing by default.
1758	var 	self = this;
1759	document.body.addEventListener(
1760		"unload",
1761		function(event) {
1762			self.uninstall();
1763		}
1764	);
1765	return this;
1766}
1767
1768
1769//  --------------------------------------------------------------------------------------
1770primitiveToServer(message, primitiveArguments, optionsArg) {
1771// Primitive are like messages except they go directly to the subclass of ApplicationServer which serves the application.
1772// A primitive will have a string specifying the message, arguments, and ajax options (i.e.,method, sync/async, etc.)
1773// A primitive is session independent (no prior session establishment is necessary)
1774
1775	$t.debug("Sending PRIMITIVE to server:", message, primitiveArguments, optionsArg);
1776	var	options,
1777		method;
1778		
1779	options = optionsArg || {};
1780	method = options.method ||  "POST";
1781	method = method.toUpperCase();
1782	var asynchronous = !(options.async == false);
1783	var data = {
1784		"arguments": JSON.stringify(primitiveArguments)
1785	};
1786	return this.serverRequest(method, message, data, asynchronous);
1787}
1788
1789
1790//  --------------------------------------------------------------------------------------
1791PUT(path, parameters) {
1792	return this.asyncMessageToServer(
1793		path,
1794		parameters,
1795		{ method: 'PUT' }
1796	);
1797}
1798
1799
1800//  --------------------------------------------------------------------------------------
1801registerInterestInEvent(eventName)  {
1802// If I haven't done that before, I will post a client event notifying server
1803// that I am interested in @eventName.
1804// Then I add @eventName property to my serverEvents with counter set to zero.
1805
1806	this.serverEvents = this.serverEvents || {};
1807	if (!this.serverEvents[eventName]) {
1808		this.postClientEvent("registerInterestInEvent", eventName);
1809		this.serverEvents[eventName] = [];
1810	}
1811}
1812
1813
1814//  --------------------------------------------------------------------------------------
1815registerServerEvents() {
1816
1817	this.registerSessionReady();
1818	this.registerSessionExpired();
1819	this.registerSessionNotFound();
1820	this.registerServerShutdown();
1821	return this;
1822}
1823
1824
1825//  --------------------------------------------------------------------------------------
1826registerServerShutdown() {
1827
1828	var self = this;
1829	this.onServerEvent(
1830		'ServerShutdown',
1831		function(data) {
1832			self.handleServerShutdown(data);
1833		}
1834	);
1835
1836	return this;
1837}
1838
1839
1840//  --------------------------------------------------------------------------------------
1841registerSessionExpired() {
1842
1843	var self = this;
1844	this.onServerEvent(
1845		'SessionExpired',
1846		function(data) {
1847			self.handleSessionExpired(data);
1848		}
1849	);
1850
1851	return this;
1852}
1853
1854
1855//  --------------------------------------------------------------------------------------
1856registerSessionNotFound() {
1857
1858	var self = this;
1859	this.onServerEvent(
1860		'SessionNotFound',
1861		function(data) {
1862			self.handleSessionNotFound(data);
1863		}
1864	);
1865
1866	return this;
1867}
1868
1869
1870//  --------------------------------------------------------------------------------------
1871registerSessionReady() {
1872
1873	var self = this;
1874
1875	this.onServerEvent(
1876		"SessionReady",
1877		function(event) {
1878			self.handleSessionReady();
1879		}
1880	);
1881
1882	return this;
1883}
1884
1885
1886//  --------------------------------------------------------------------------------------
1887removeEventListener(type, listener, useCapture) {
1888// Simulate the EventTarget API
1889
1890	if (! this._eventTarget) { return null };
1891	return this._eventTarget.removeEventListener(type, listener, useCapture || false)
1892}
1893
1894
1895//  --------------------------------------------------------------------------------------
1896removeItemFromStorage(key, storage) {
1897	// Get an item under the @key from the browser @storage, then remove it.
1898	// @storage can be sessionStorage or localStorage.
1899	var item = this.getItemFromStorage(key, storage);
1900	storage.removeItem(key);
1901	return item;
1902}
1903
1904
1905//  --------------------------------------------------------------------------------------
1906serverRequest (method, path, data, asynchronous) {
1907	/* Build a generic ajax message that will handle both synchronous and asynchronous requests,
1908	* on any browser(?).  Tested on Chrome, Firefox, IE.
1909	*/
1910
1911	var application = this,
1912		url = this.url,
1913		returnData = {};
1914	var questionMarkIndex = url.indexOf('?'); 
1915	var urlWithoutQuery; 
1916	//if url contains a query string, insert the extension to the path before the question mark
1917	if(questionMarkIndex > 0) { 
1918		urlWithoutQuery = url.slice(0,  questionMarkIndex);
1919		//add trailing slash if missing
1920		if (urlWithoutQuery[urlWithoutQuery.length-1] != "/") { urlWithoutQuery += "/" };
1921		url = urlWithoutQuery + path +  url.slice(questionMarkIndex);
1922	} else {
1923		//add trailing slash if missing
1924		if (url[url.length-1] != "/") { url += "/" };
1925		url += path;
1926	}
1927	var request = new AppeX.ServerRequest(url, data, method, asynchronous);
1928	$t.debug("Sending Server Request",request);
1929	request.deliver();
1930	$t.debug("Received Server Response", request.response);
1931	return request.response;
1932}
1933
1934
1935//  --------------------------------------------------------------------------------------
1936sessionQuery() {
1937	if (!this.session) return null;
1938	var sessionKey = this.session.sessionKey;
1939	return sessionKey + "=" + this[sessionKey];
1940}
1941
1942
1943//  --------------------------------------------------------------------------------------
1944setTheme(url) {
1945	// Set the href of the #appex-theme-css element to @url.
1946	// This will cause the theme link CSS to be reloaded and the page re-styled according to the new style sheet.
1947	var 	link = document.getElementById("appex-theme-css");
1948	
1949	if (!link) {
1950		return;
1951	}
1952	
1953	link.href = url;
1954}
1955
1956
1957//  --------------------------------------------------------------------------------------
1958showSessionExpired(session) {
1959	alert ("Session expired.");
1960	return this
1961}
1962
1963
1964//  --------------------------------------------------------------------------------------
1965storeItemInStorage(key, item, storage) {
1966	// Store an item under the @key in the browser @storage.
1967	// @storage can be sessionStorage or localStorage.
1968	storage.setItem(key, JSON.stringify(item));
1969}
1970
1971
1972//  --------------------------------------------------------------------------------------
1973syncMessageToServer(path, messageArguments, messageOptions) {
1974	/* Force the message to be sent synchronously. */
1975	
1976	var args, 
1977		options;
1978
1979	args = messageArguments || [];
1980	options = messageOptions || {};
1981
1982	options.async = false;
1983
1984	return this.messageToServer(path, args, options).object;
1985}
1986
1987
1988//  --------------------------------------------------------------------------------------
1989uninstall() {
1990// Tear down things that need to be torn down when the window gets closed, reloaded, or navigated away.
1991
1992	this.closeEventChannel();
1993}
1994
1995
1996//  --------------------------------------------------------------------------------------
1997updateSessionData(session, data) {
1998// Update a @session object from @data.
1999	$t.replace(session, data);
2000	$t.debug("Update Session:", session);
2001	this.storeItemInStorage("appex_session", session, window.sessionStorage);
2002	// For easy access, store the value of 'sessionId' in myself.
2003	this[session.sessionKey] = session[session.sessionKey];
2004	this.session = session;
2005	return this;
2006}
2007
2008
2009};
2010

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.