bigpipe.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. //==============================================================================
  2. // BigPipe Module
  3. //==============================================================================
  4. BigPipe = (function() {
  5. "use strict";
  6. //==============================================================================
  7. // PhaseDoneJS: Responsible for Pagelet and Resource
  8. //==============================================================================
  9. const PhaseDoneJS = {
  10. //==============================================================================
  11. // Increase phase and execute callbacks
  12. //==============================================================================
  13. handler(context, phase) {
  14. for(let currentPhase = context.phase; currentPhase <= phase; ++currentPhase) {
  15. this.execute(context, currentPhase);
  16. }
  17. return context.phase = ++phase;
  18. },
  19. //==============================================================================
  20. // Execute callbacks of the given phase
  21. //==============================================================================
  22. execute(context, phase) {
  23. context.phaseDoneJS[phase].forEach(function(code) {
  24. try {
  25. window.eval.call(window, code);
  26. } catch(e) {
  27. console.error("PhaseDoneJS: " + e);
  28. }
  29. });
  30. }
  31. };
  32. //==============================================================================
  33. // Resource: Represents a resource
  34. //==============================================================================
  35. class Resource {
  36. constructor(data, type) {
  37. this.ID = data.ID;
  38. this.HREF = data.HREF;
  39. this.callbacks = [];
  40. this.node = false;
  41. this.done = false;
  42. this.type = type;
  43. this.phaseDoneJS = data.PHASE;
  44. this.phase = 0;
  45. PhaseDoneJS.handler(this, Resource.PHASE_INIT);
  46. }
  47. //==============================================================================
  48. // Resource types
  49. //==============================================================================
  50. static get TYPE_STYLESHEET() { return 0; }
  51. static get TYPE_JAVASCRIPT() { return 1; }
  52. //==============================================================================
  53. // Phase numbers for PhaseDoneJS
  54. //==============================================================================
  55. static get PHASE_INIT() { return 0; }
  56. static get PHASE_LOAD() { return 1; }
  57. static get PHASE_DONE() { return 2; }
  58. //==============================================================================
  59. // Loading the resource
  60. //==============================================================================
  61. execute() {
  62. switch(this.type) {
  63. case Resource.TYPE_STYLESHEET:
  64. this.node = document.createElement("link");
  65. this.node.setAttribute("rel", "stylesheet");
  66. this.node.setAttribute("href", this.HREF);
  67. break;
  68. case Resource.TYPE_JAVASCRIPT:
  69. this.node = document.createElement("script");
  70. this.node.setAttribute("src", this.HREF);
  71. this.node.setAttribute("async", "async");
  72. break;
  73. default:
  74. return false;
  75. }
  76. const callback = () => {
  77. PhaseDoneJS.handler(this, Resource.PHASE_DONE);
  78. this.executeCallbacks();
  79. };
  80. this.node.onload = callback;
  81. this.node.onerror = callback;
  82. document.head.appendChild(this.node);
  83. PhaseDoneJS.handler(this, Resource.PHASE_LOAD);
  84. }
  85. //==============================================================================
  86. // Register a new callback
  87. //==============================================================================
  88. registerCallback(callback) {
  89. return this.callbacks.push(callback);
  90. }
  91. //==============================================================================
  92. // Executes all registered callbacks
  93. //==============================================================================
  94. executeCallbacks() {
  95. if(!this.done && (this.done = true)) {
  96. this.callbacks.forEach(function(callback) {
  97. callback();
  98. });
  99. }
  100. }
  101. //==============================================================================
  102. // Remove callbacks after abort of loading the resource
  103. //==============================================================================
  104. abortLoading() {
  105. if(this.node) {
  106. this.node.onload = null;
  107. this.node.onerror = null;
  108. // Remove element from DOM
  109. let parentNode = this.node.parentNode;
  110. return parentNode.removeChild(this.node);
  111. }
  112. }
  113. }
  114. //==============================================================================
  115. // Pagelet: Represents a pagelet
  116. //==============================================================================
  117. class Pagelet {
  118. constructor(data, HTML) {
  119. this.ID = data.ID;
  120. this.NEED = data.NEED;
  121. this.HTML = HTML;
  122. this.JSCode = data.CODE;
  123. this.phaseDoneJS = data.PHASE;
  124. this.stylesheets = data.RSRC[Resource.TYPE_STYLESHEET];
  125. this.javascripts = data.RSRC[Resource.TYPE_JAVASCRIPT];
  126. this.phase = 0;
  127. this.resources = [[], []];
  128. PhaseDoneJS.handler(this, Pagelet.PHASE_INIT);
  129. }
  130. //==============================================================================
  131. // Phase numbers for PhaseDoneJS
  132. //==============================================================================
  133. static get PHASE_INIT() { return 0; }
  134. static get PHASE_LOADCSS() { return 1; }
  135. static get PHASE_HTML() { return 2; }
  136. static get PHASE_LOADJS() { return 3; }
  137. static get PHASE_DONE() { return 4; }
  138. //==============================================================================
  139. // Initialize and execute the CSS resources
  140. //==============================================================================
  141. execute() {
  142. this.initializeResources();
  143. if(!this.executeResources(Resource.TYPE_STYLESHEET)) {
  144. this.replaceHTML();
  145. }
  146. }
  147. //==============================================================================
  148. // Initialize the pagelet resources
  149. //==============================================================================
  150. initializeResources() {
  151. this.stylesheets.forEach(data => {
  152. this.attachResource(new Resource(data, Resource.TYPE_STYLESHEET));
  153. });
  154. this.javascripts.forEach(data => {
  155. this.attachResource(new Resource(data, Resource.TYPE_JAVASCRIPT));
  156. });
  157. }
  158. //==============================================================================
  159. // Executes all resources of the specific type
  160. //==============================================================================
  161. executeResources(type) {
  162. let somethingExecuted = false;
  163. this.resources[type].forEach(function(resource) {
  164. somethingExecuted = true;
  165. resource.execute();
  166. });
  167. return somethingExecuted;
  168. }
  169. //==============================================================================
  170. // Attach a new resource to the pagelet
  171. //==============================================================================
  172. attachResource(resource) {
  173. switch(resource.type) {
  174. case Resource.TYPE_STYLESHEET:
  175. resource.registerCallback(() => this.onStylesheetLoaded());
  176. break;
  177. case Resource.TYPE_JAVASCRIPT:
  178. resource.registerCallback(() => this.onJavascriptLoaded());
  179. break;
  180. }
  181. return this.resources[resource.type].push(resource);
  182. }
  183. //==============================================================================
  184. // Replaces the placeholder node HTML
  185. //==============================================================================
  186. replaceHTML() {
  187. document.getElementById(this.ID).innerHTML = this.HTML;
  188. PhaseDoneJS.handler(this, Pagelet.PHASE_HTML);
  189. BigPipe.onPageletHTMLreplaced(this.ID);
  190. }
  191. //==============================================================================
  192. // Executes the inline javascript code of the pagelet
  193. //==============================================================================
  194. executeInlineJavascript() {
  195. this.JSCode.forEach(code => {
  196. try {
  197. window.eval.call(window, code);
  198. } catch(e) {
  199. console.error(this.ID + ": " + e);
  200. }
  201. });
  202. PhaseDoneJS.handler(this, Pagelet.PHASE_DONE);
  203. }
  204. //==============================================================================
  205. // Executed each time when a stylesheet resource has been loaded
  206. //==============================================================================
  207. onStylesheetLoaded() {
  208. if(this.resources[Resource.TYPE_STYLESHEET].every(function(resource){
  209. return resource.done;
  210. })) {
  211. PhaseDoneJS.handler(this, Pagelet.PHASE_LOADCSS);
  212. this.replaceHTML();
  213. }
  214. }
  215. //==============================================================================
  216. // Executed each time when a javascript resource has been loaded
  217. //==============================================================================
  218. onJavascriptLoaded() {
  219. if(this.resources[Resource.TYPE_JAVASCRIPT].every(function(resource){
  220. return resource.done;
  221. })) {
  222. PhaseDoneJS.handler(this, Pagelet.PHASE_LOADJS);
  223. this.executeInlineJavascript();
  224. }
  225. }
  226. }
  227. //==============================================================================
  228. // BigPipe
  229. //==============================================================================
  230. const BigPipe = {
  231. pagelets: [],
  232. phase: 0,
  233. done: [],
  234. wait: [],
  235. interval: null,
  236. onPageletArrive(data, codeContainer) {
  237. let pageletHTML = codeContainer.innerHTML;
  238. pageletHTML = pageletHTML.substring(5, pageletHTML.length - 4);
  239. codeContainer.parentNode.removeChild(codeContainer);
  240. let pagelet = new Pagelet(data, pageletHTML);
  241. this.pagelets.push(pagelet);
  242. if(this.phase === 0) {
  243. this.phase = 1;
  244. }
  245. if(pagelet.NEED.length === 0 || pagelet.NEED.every(function(needID) {
  246. return BigPipe.done.indexOf(needID) !== -1;
  247. })) {
  248. pagelet.execute();
  249. }
  250. else {
  251. this.wait.push(pagelet);
  252. }
  253. },
  254. onLastPageletArrived() {
  255. this.phase = 2;
  256. this.interval = setInterval(() => {
  257. if(this.done.length === this.pagelets.length) {
  258. clearInterval(this.interval);
  259. this.executeJavascriptResources();
  260. }
  261. }, 50);
  262. },
  263. onPageletHTMLreplaced(pageletID) {
  264. BigPipe.done.push(pageletID);
  265. for(let i = 0; i < this.wait.length; ++i) {
  266. let pagelet = this.wait[i];
  267. // Check if all IDs from NEED exists within BigPipe.done
  268. // If this is true, then all required dependencies are satisfied.
  269. if(pagelet.NEED.every(function(needID){
  270. return BigPipe.done.indexOf(needID) !== -1;
  271. })) {
  272. BigPipe.wait.splice(i--, 1); // remove THIS pagelet from wait list
  273. pagelet.execute();
  274. }
  275. }
  276. },
  277. executeJavascriptResources() {
  278. this.phase = 3;
  279. this.pagelets.forEach(function(pagelet) {
  280. if(!pagelet.executeResources(Resource.TYPE_JAVASCRIPT)) {
  281. pagelet.onJavascriptLoaded();
  282. }
  283. });
  284. }
  285. };
  286. //==============================================================================
  287. // Public-Access
  288. //==============================================================================
  289. return {
  290. onPageletArrive(data, codeContainer) {
  291. BigPipe.onPageletArrive(data, codeContainer);
  292. },
  293. onLastPageletArrived() {
  294. BigPipe.onLastPageletArrived();
  295. },
  296. reset() {
  297. BigPipe.pagelets.forEach(function(pagelet) {
  298. pagelet.resources[Resource.TYPE_STYLESHEET].forEach(function(resource) {
  299. resource.abortLoading();
  300. });
  301. pagelet.resources[Resource.TYPE_JAVASCRIPT].forEach(function(resource) {
  302. resource.abortLoading();
  303. });
  304. });
  305. try {
  306. window.stop();
  307. } catch(e) {
  308. document.execCommand('Stop');
  309. }
  310. clearInterval(BigPipe.interval);
  311. BigPipe.pagelets = [];
  312. BigPipe.phase = 0;
  313. BigPipe.wait = [];
  314. BigPipe.done = [];
  315. }
  316. };
  317. })();