From c5637489e603c588fca41e2b7bd4345b67914f33 Mon Sep 17 00:00:00 2001 From: Thomas Lange Date: Thu, 23 Jun 2016 13:10:24 +0200 Subject: Pagelets executed immediately; Dependency feature; Several improvements + All pagelets are now executed immediately on arrive. + A "Display Dependency" feature was added. If you give a pagelet an dependency, it will first be executed if all pagelets, who registered as dependency, are displayed. + If BigPipe.reset() is called, the function loops through each Resource and executes Resource.abortLoading(). This removes the or '."\n"; - echo ''."\n"; echo ''."\n\n"; } diff --git a/include/classes/BigPipe/BigPipe.php b/include/classes/BigPipe/BigPipe.php index 8cdda0b..1af4d8a 100755 --- a/include/classes/BigPipe/BigPipe.php +++ b/include/classes/BigPipe/BigPipe.php @@ -34,7 +34,7 @@ class BigPipe { #=============================================================================== private static function singleResponse(Pagelet $Pagelet, $last = FALSE) { $pageletJSON = [ - 'ID' => $Pagelet->getID(), + 'ID' => $Pagelet->getID(), 'NEED' => $Pagelet->getDependencies(), 'RESOURCES' => ['CSS' => $Pagelet->getCSSFiles(), 'JS' => $Pagelet->getJSFiles(), 'JS_CODE' => removeLineBreaksAndTabs($Pagelet->getJSCode())], 'PHASES' => (object) $Pagelet->getPhaseDoneJS() ]; diff --git a/include/classes/BigPipe/DemoPagelet.php b/include/classes/BigPipe/DemoPagelet.php index 83689ba..adb9f89 100755 --- a/include/classes/BigPipe/DemoPagelet.php +++ b/include/classes/BigPipe/DemoPagelet.php @@ -3,8 +3,8 @@ namespace BigPipe; class DemoPagelet extends Pagelet { - public function __construct($customID = NULL, $priority = Pagelet::PRIORITY_NORMAL) { - parent::__construct($customID, $priority); + public function __construct($customID = NULL, $priority = Pagelet::PRIORITY_NORMAL, array $dependencies = []) { + parent::__construct($customID, $priority, $dependencies); $message = '%s: PhaseDoneJS for phase %s'; diff --git a/include/classes/BigPipe/Pagelet.php b/include/classes/BigPipe/Pagelet.php index 22aadce..436e12a 100755 --- a/include/classes/BigPipe/Pagelet.php +++ b/include/classes/BigPipe/Pagelet.php @@ -8,6 +8,7 @@ class Pagelet { private $JSFiles = []; private $CSSFiles = []; private $phaseDoneJS = []; + private $dependencies = []; private $tagname = 'div'; private static $count = 0; @@ -29,8 +30,9 @@ class Pagelet { const PHASE_LOADJS = 3; # After all the JS resources have been loaded const PHASE_EXECJS = 4; # After the static JS code has been executed - public function __construct($customID = NULL, $priority = self::PRIORITY_NORMAL) { + public function __construct($customID = NULL, $priority = self::PRIORITY_NORMAL, array $dependencies = []) { $this->phaseDoneJS = array_pad($this->phaseDoneJS, 5, []); + $this->dependencies = $dependencies; $this->ID = is_string($customID) ? $customID : 'P'.++self::$count; BigPipe::addPagelet($this, $priority); @@ -113,6 +115,13 @@ class Pagelet { return $this->phaseDoneJS; } + #=============================================================================== + # Return all display dependencies + #=============================================================================== + public function getDependencies(): array { + return $this->dependencies; + } + #=============================================================================== # Set custom placeholder tagname #=============================================================================== diff --git a/include/pagelets.php b/include/pagelets.php new file mode 100644 index 0000000..176bc74 --- /dev/null +++ b/include/pagelets.php @@ -0,0 +1,50 @@ +addHTML('
I AM A PAGELET WITH RED BACKGROUND
'); +$PageletRed->addCSS('static/red.php'); +$PageletRed->addCSS('static/red.php'); +$PageletRed->addJS('static/delayJS.php'); +$PageletRed->addJSCode("document.getElementById('red').innerHTML += ' [JS executed]';document.getElementById('red').style.borderRadius = '30px';"); + +#=============================================================================== +# Pagelet with blue background color +#=============================================================================== +$PageletBlue = new BigPipe\DemoPagelet('bluePL', BigPipe\Pagelet::PRIORITY_HIGH); +$PageletBlue->addHTML('
I AM A PAGELET WITH BLUE BACKGROUND
'); +$PageletBlue->addCSS('static/blue.php'); +$PageletRed->addCSS('static/red.php'); +$PageletBlue->addJS('static/delayJS.php'); +$PageletBlue->addJSCode("document.getElementById('blue').innerHTML += ' [JS executed]';document.getElementById('blue').style.borderRadius = '30px';"); + +#=============================================================================== +# Pagelet with green background color +#=============================================================================== +$PageletGreen = new BigPipe\DemoPagelet('greenPL'); + +{ + #=============================================================================== + # Pagelet within $PageletGreen + #=============================================================================== + // The third parameter is required to ensure that the $InnerPagelet will only be + // executed if the HTML from the $PageletGreen has ALREADY DISPLAYED. Otherwise, + // $InnerPagelet would not find his placeholder tag which is defined WITHIN the + // HTML on $PageletGreen. Of course, you can still add other pagelets as + // dependency. Then will $InnerPagelet only displayed if all dependencies are + // already displayed! + // + // NOTE: PRIORITY_HIGHEST is only set so that you can see, that this pagelet is + // the first which arrives, but it will first be displayed if his dependency + // pagelets are already displayed. + + $InnerPagelet = new BigPipe\DemoPagelet('innerPL', BigPipe\Pagelet::PRIORITY_HIGHEST, [$PageletGreen->getID()]); + $InnerPagelet->addHTML('
Inner Pagelet \(o_o)/
'); +} + +$PageletGreen->addHTML('
I AM A PAGELET WITH GREEN BACKGROUND'.$InnerPagelet.'
'); +$PageletGreen->addCSS('static/green.php'); +$PageletGreen->addJS('static/delayJS.php'); +$PageletGreen->addJSCode("document.getElementById('green').innerHTML += ' [JS executed]';document.getElementById('green').style.borderRadius = '30px';"); +?> \ No newline at end of file diff --git a/index.php b/index.php index 1eae51e..5f12fd6 100755 --- a/index.php +++ b/index.php @@ -20,32 +20,8 @@ if(isset($_GET['bigpipe']) AND (int) $_GET['bigpipe'] === 0) { BigPipe\BigPipe::enablePipeline(FALSE); } -#=============================================================================== -# Pagelet with red background color -#=============================================================================== -$PageletRed = new BigPipe\DemoPagelet(); -$PageletRed->addHTML('
I AM A PAGELET WITH RED BACKGROUND
'); -$PageletRed->addCSS('static/red.php'); -$PageletRed->addJS('static/delayJS.php'); -$PageletRed->addJSCode("document.getElementById('red').innerHTML += ' [JS executed]';document.getElementById('red').style.borderRadius = '30px';"); - -#=============================================================================== -# Pagelet with blue background color -#=============================================================================== -$PageletBlue = new BigPipe\DemoPagelet('customPageletID', BigPipe\Pagelet::PRIORITY_HIGH); -$PageletBlue->addHTML('
I AM A PAGELET WITH BLUE BACKGROUND
'); -$PageletBlue->addCSS('static/blue.php'); -$PageletBlue->addJS('static/delayJS.php'); -$PageletBlue->addJSCode("document.getElementById('blue').innerHTML += ' [JS executed]';document.getElementById('blue').style.borderRadius = '30px';"); - -#=============================================================================== -# Pagelet with green background color -#=============================================================================== -$PageletGreen = new BigPipe\DemoPagelet(); -$PageletGreen->addHTML('
I AM A PAGELET WITH GREEN BACKGROUND
'); -$PageletGreen->addCSS('static/green.php'); -$PageletGreen->addJS('static/delayJS.php'); -$PageletGreen->addJSCode("document.getElementById('green').innerHTML += ' [JS executed]';document.getElementById('green').style.borderRadius = '30px';"); +// Outsourced to avoid duplicate code in index.php and async.php +require_once 'include/pagelets.php'; ?> diff --git a/static/bigpipe.js b/static/bigpipe.js index 30d1f55..5139483 100755 --- a/static/bigpipe.js +++ b/static/bigpipe.js @@ -8,56 +8,71 @@ var BigPipe = (function() { function Resource(resourceURL, type) { this.resourceURL = resourceURL; this.callbacks = []; + this.node = false; this.done = false; this.type = type; } + //============================================================================== + // Resource: Register a new callback + //============================================================================== + Resource.prototype.registerCallback = function(callback) { + return this.callbacks.push(callback); + }; + + //============================================================================== + // Resource: Executes all registered callbacks + //============================================================================== + Resource.prototype.executeCallbacks = function() { + if(!this.done) { + this.done = true; + + this.callbacks.forEach(function(callback) { + callback(); + }); + } + }; + //============================================================================== // Resource: Loading the resource //============================================================================== - Resource.prototype.start = function() { + Resource.prototype.execute = function() { if(this.type === 0) { - var element = document.createElement('link'); - element.setAttribute('rel', 'stylesheet'); - element.setAttribute('href', this.resourceURL); + this.node = document.createElement('link'); + this.node.setAttribute('rel', 'stylesheet'); + this.node.setAttribute('href', this.resourceURL); } else { - var element = document.createElement('script'); - element.setAttribute('src', this.resourceURL); - element.async = true; + this.node = document.createElement('script'); + this.node.setAttribute('src', this.resourceURL); + this.node.async = true; } - element.setAttribute('class', 'bigpipe'); + this.node.setAttribute('class', 'bigpipe'); - document.head.appendChild(element); + document.head.appendChild(this.node); - element.onload = function() { + this.node.onload = function() { this.executeCallbacks(); }.bind(this); - element.onerror = function() { + this.node.onerror = function() { this.executeCallbacks(); }.bind(this); }; //============================================================================== - // Resource: Register a new callback + // Resource: Remove callbacks after abort of loading the resource //============================================================================== - Resource.prototype.registerCallback = function(callback) { - return this.callbacks.push(callback); - }; + Resource.prototype.abortLoading = function() { + if(this.node) { + this.node.onload = function(){}; + this.node.onerror = function(){}; - //============================================================================== - // Resource: Executes all registered callbacks - //============================================================================== - Resource.prototype.executeCallbacks = function() { - if(!this.done) { - this.done = true; - - this.callbacks.forEach(function(callback) { - callback(); - }); + // Remove element from DOM + var parentNode = this.node.parentNode; + return parentNode.removeChild(this.node); } }; @@ -66,16 +81,19 @@ var BigPipe = (function() { //============================================================================== function Pagelet(data, HTML) { this.pageletID = data.ID; - this.HTML = HTML || ""; + this.HTML = HTML; this.CSSFiles = data.RESOURCES.CSS; this.JSFiles = data.RESOURCES.JS; this.JSCode = data.RESOURCES.JS_CODE; + this.NEED = data.NEED; this.phase = 0; this.CSSResources = []; this.JSResources = []; this.phaseDoneJS = data.PHASES; + + this.phaseDoneHandler(0); } //============================================================================== @@ -103,9 +121,9 @@ var BigPipe = (function() { }; //============================================================================== - // Pagelet: Initialize and start the CSS resources + // Pagelet: Initialize and execute the CSS resources //============================================================================== - Pagelet.prototype.start = function() { + Pagelet.prototype.execute = function() { var isStarted = false; this.CSSFiles.forEach(function(resourceURL) { @@ -118,11 +136,11 @@ var BigPipe = (function() { this.CSSResources.forEach(function(resource) { isStarted = true; - resource.start(); + resource.execute(); }.bind(this)); // If no CSS resource was started (= no external CSS resources exists), then begin to inject the HTML - !isStarted && this.injectHTML(); + !isStarted && this.replaceHTML(); }; //============================================================================== @@ -152,6 +170,7 @@ var BigPipe = (function() { console.error(this.pageletID + ": " + e); } }); + this.phaseDoneHandler(4); }; //============================================================================== @@ -159,11 +178,10 @@ var BigPipe = (function() { //============================================================================== Pagelet.prototype.onloadJS = function() { if(this.phase === 3 && this.JSResources.every(function(resource){ - return resource.done; - })) { + return resource.done; + })) { this.phaseDoneHandler(3); this.executeJSCode(); - this.phaseDoneHandler(4); } }; @@ -172,28 +190,22 @@ var BigPipe = (function() { //============================================================================== Pagelet.prototype.onloadCSS = function() { if(this.CSSResources.every(function(resource){ - return resource.done; - })) { - this.injectHTML(); + return resource.done; + })) { + this.phaseDoneHandler(1); + this.replaceHTML(); } }; //============================================================================== // Pagelet: Injects the HTML content into the DOM //============================================================================== - Pagelet.prototype.injectHTML = function() { - this.phaseDoneHandler(1); - + Pagelet.prototype.replaceHTML = function() { document.getElementById(this.pageletID).innerHTML = this.HTML; this.phaseDoneHandler(2); - BigPipe.executeNextPagelet(); - - // Check if this was the last pagelet and then start loading of the external JS resources - if(BigPipe.phase === 2 && BigPipe.pagelets[BigPipe.pagelets.length - 1].pageletID === this.pageletID) { - BigPipe.loadJSResources(); - } + BigPipe.pageletHTMLreplaced(this.pageletID); }; //============================================================================== @@ -202,17 +214,8 @@ var BigPipe = (function() { var BigPipe = { pagelets: [], phase: 0, - offset: 0, - - executeNextPagelet: function() { - if(this.pagelets[this.offset]) { - this.pagelets[this.offset++].start(); - } - - else if(this.phase < 2) { - setTimeout(this.executeNextPagelet.bind(this), 20); - } - }, + done: [], + wait: [], onPageletArrive: function(data, codeContainer) { var pageletHTML = codeContainer.innerHTML; @@ -220,43 +223,64 @@ var BigPipe = (function() { codeContainer.parentNode.removeChild(codeContainer); var pagelet = new Pagelet(data, pageletHTML); - pagelet.phaseDoneHandler(0); - if(this.pagelets.push(pagelet) && this.phase === 0 && !data.IS_LAST) { + this.pagelets.push(pagelet); + + if(this.phase = 0) { this.phase = 1; - this.executeNextPagelet(); } - else if(data.IS_LAST) { + if(data.IS_LAST) { this.phase = 2; - if(this.pagelets.length === 1) { - this.executeNextPagelet(); + } + + if(pagelet.NEED.length === 0 || pagelet.NEED.every(function(needID) { + return BigPipe.done.indexOf(needID) !== -1; + })) { + pagelet.execute(); + } + + else { + this.wait.push(pagelet); + } + }, + + pageletHTMLreplaced: function(pageletID) { + BigPipe.done.push(pageletID); + + for(var i = 0; i < this.wait.length; ++i) { + var pagelet = this.wait[i]; + + // Check if all IDs from NEED exists within BigPipe.done + // If this is true, then all required dependencies are satisfied. + if(pagelet.NEED.every(function(needID){ + return BigPipe.done.indexOf(needID) !== -1; + })) { + BigPipe.wait.splice(i--, 1); // remove THIS pagelet from wait list + pagelet.execute(); } } + + // Check if this was the last pagelet and then execute loading of the external JS resources + if(BigPipe.phase === 2 && BigPipe.done.length === BigPipe.pagelets.length ) { + BigPipe.executeJSResources(); + } }, - loadJSResources: function() { + executeJSResources: function() { this.phase = 3; - var isLoading = false; - this.pagelets.forEach(function(Pagelet) { - if(Pagelet.JSResources.length === 0) { - Pagelet.onloadJS(); + this.pagelets.forEach(function(pagelet) { + if(pagelet.JSResources.length === 0) { + pagelet.onloadJS(); } - }); - this.pagelets.forEach(function(Pagelet) { - Pagelet.JSResources.forEach(function(Resource) { - Resource.start(); - isLoading = true; - }); + else { + pagelet.JSResources.forEach(function(resource) { + resource.execute(); + }); + } }); - - if(!isLoading) { - this.pagelets.forEach(function(Pagelet) { - Pagelet.onloadJS(); - }); - } } }; @@ -269,15 +293,18 @@ var BigPipe = (function() { }, reset: function() { - BigPipe.pagelets = []; - BigPipe.offset = 0; - BigPipe.phase = 0; + BigPipe.pagelets.forEach(function(pagelet) { + pagelet.CSSResources.concat(pagelet.JSResources).forEach(function(resource) { + resource.abortLoading(); + }); + }); - var resources = document.head.getElementsByClassName('bigpipe'); + window.stop() || document.execCommand("Stop"); - while(resources[0]) { - resources[0].parentNode.removeChild(resources[0]); - } + BigPipe.pagelets = []; + BigPipe.phase = 0; + BigPipe.wait = []; + BigPipe.done = []; } }; })(); \ No newline at end of file -- cgit v1.2.3