Monday, June 4, 2018

Old Phone/Tablet as an Info Board Part 3: IFRAME with PHP capture-and-restyle

In an earlier post, we discussed that using iframe for an information widget on our screen is perhaps the most universal, but the least versatile because without the ability to interact with the content of the iframe, you cannot customize anything at all about the way your information is presented. So if you happen to need to reorganize your information to fit your needs, bad luck.

Luckily I have a Synology DiskStation to host my screen, and it can function as a PHP web server. So the idea becomes to fetch the target web page by the server and echo it back to the client, using code like this :

<?php
$opts = array('http'=>array('header' => "User-Agent:MyAgent/1.0\r\n"));
$context = stream_context_create($opts);
$code = file_get_contents("http://your_URL_goes_here",false,$context); 
echo $code;
?> 

Here $opts and $context are needed to appear to the target host as a legitimate HTTP request so it won't respond with something like Error 403. Now, if you put the above into a file called, e.g., frame.php, you can then embed it into your interface using our old <iframe src="frame.php"...> tag.

As an immediate benefit, PHP get_file_contents is not a browser, so it will retrieve only the basic HTML (hopefully containing the information you are after). This makes the resulting iframe a whole lot, big time easier on the client browser!

On the flipside, this also means that you will need to do the styling and formatting yourself.

The first method that we cover is something I called "capture-and-restyle" or "CSS injection". In a nutshell, the idea is to save a local copy of the target site resources (mainly, CSS styles and images), which you can then edit to suit your needs. This method is best suitable if:
  • Most of the target page contents will be used;
  • The target page, as formatted, looks more or less the way you want it to look on the board;
  • The target page is relatively simple - you don't want to sift through hundreds of styles manually.
As an example, we use the GO transit (suburban commuter trains in the Toronto area) mobile departure board page:
http://gotracker.ca/GoTracker/mobile/StationStatus/Service/01/Station/7

It is already "kind of" optimized for viewing on small screens, so we will only need to make a few minor adjustments. So it makes sense to reuse as much of the original styling as we can.


The workflow is as follows:
  1. Save local copies of styles and images that you want to display in your page. (The simplest way of doing this is to save the complete webpage in Chrome and look for items). Upload these on the DiskStation in a subfolder (e.g. go/) to the same folder where your index.html resides:
    Important! Make sure that you grant Execute permissions to the images! (To do this, right click the items in the File Station browser and choose Properties.)
  2. Add the following to the PHP script right before echo $code, redirecting requests to styles and images to local copies:
    $code = str_replace('/GOTracker/mobile/', 'go/', $code);
    $code = str_replace('/GoTracker/mobile/', 'go/', $code);
    $code = str_replace('../../../../', 'go/', $code);
    
    At this point, your local PHP, entered in your local browser (e.g. http://192.168.0.xxx/frame.php) should look (more or less) exactly as the target page typed in the same browser. If it does not, look for the missing elements / check permissions. Your web page inspector in Chrome or Safari is your friend here.
  3. Edit the locally saved styles/images to augment the way the widget looks. Basically, in this case, we want to hide the elements that are irrelevant to us, such as the logos; shrink the sign of the direction banners (they are obvious to us); and adjust the size of the information bearing elements so that they format nicely in a small-sized widget. I ended up adding a chunk of CSS code to the end of the stylesheet that loads last (GOGrid.css), more or less like so:
    * {border: none !important; font-family: Arial, Helvetica, sans-serif !important; font-stretch: semi-condensed;}
    #frontImg {visibility: hidden !important; height: 0px !important;}
    .imageButtonLink {visibility: hidden !important; height: 0px !important;}
     
    .sTbl {table-layout: fixed;}
    .SecondTitle > td:nth-child(1), .SecondTitle > td:nth-child(3) {width:0px ;}
    .SecondTitle > td:nth-child(2) { text-align: left !important; font-stretch: none; font-weight: bold;}
     
    .headerTR {visibility: hidden !important; height: 0px !important; font-size:0px;}
    .directionHeaderTH {font-size:6px ;}
    .bottomDoubleRowTR * {font-weight: normal; font-size: 10px;}
    .oddRowTR:nth-child(even) {background:rgb(225,255,225);}
    .feedbackLink {visibility: hidden !important; height: 0px !important; font-size:0px;}
     
    .currentDateMain {position: fixed !important; visibility: visible !important; top: 5px !important; right: 0px;}
    #lblCurrentDateMain {color: rgb(128,150,128); text-shadow: none !important; } 
    #lblCurrentTimeMain {color: rgb(0,128,0); font-weight: bold; text-shadow: none !important; }
    

    (Note the line with nth-child(even): this introduces the striped table style for ease of readability. Apparently this was originally in mind of the website programmers, since the class name, .oddRowTR, kind of suggests that there should also be .evenRowTR with different styling; however in practice all rows are of the .oddRowTR class, so, well, we fixed this.:)
  4. Done - it ended up looking like so:



So here is the final version of the PHP
<?php
$opts = array('http'=>array('header' => "User-Agent:MyAgent/1.0\r\n"));
$context = stream_context_create($opts);
$line=$_GET["line"];
$stn=$_GET["station"];
$code = file_get_contents("http://gotracker.ca/GoTracker/mobile/StationStatus/Service/".$line."/Station/".$stn,false,$context); 
$code = str_replace('/GOTracker/mobile/', 'go/', $code);
$code = str_replace('/GoTracker/mobile/', 'go/', $code);
$code = str_replace('../../../../', 'go/', $code);
 
$code = str_replace('Union Station', 'Union Stn', $code);
$code = str_replace('Clarkson GO', 'Clarkson', $code);
$code = str_replace('Erindale GO', 'Erindale', $code);
$code = str_replace('On Time', 'OK', $code);
 
echo $code;
?> 
Notice that I added some parametric functionality to reuse the same PHP for two stations as in the original layout. The last four str_replace are for cosmetic purposes - to make the individual departure lines fit on a single line as often as possible. There will still be occasional ugly misses, but (1) they can be corrected upon discovery, and (2) nothing is perfect, so why bother.

and final HTML
<div id="train1" class="basic" style="position: absolute; left: 150px; top: 10px; width: 200px; height: 195px; ">
<iframe id="go1" scrolling="no" src="go.php?line=21&station=554" style="height: 190px; width: 200px; border: none;"> </iframe>
</div>
<div id="train2" class="basic" style="position: absolute; left: 365px; top: 10px; width: 200px; height: 195px; ">
<iframe id="go2" scrolling="no" src="go.php?line=01&station=7" style="height: 190px; width: 200px;  border: none;"> </iframe>
</div>

As another big benefit, the source of the iframe now has the same origin as your main page. This means that you are now fully in control of its contents, which can now be accessed and manipulated from the script. We will make extensive use of this feature in the next post where we describe another PHP+iframe combo to make our hourly weather forecast widget. Here, we limit the use of this feature to a simple example: dim the widget if it has no relevant information. Let us do it like this:
<div id="mask1" class="basic" style="opacity: 0.75; visibility: hidden; position: absolute; left: 150px; top: 10px; width: 200px; height: 195px; "></div>
<div id="mask2" class="basic" style="opacity: 0.75; visibility: hidden; position: absolute; left: 365px; top: 10px; width: 200px; height: 195px; "></div>
function trainhide()
{
var dom = $("iframe#go1").contents().find(".oddRowTR");
$("#mask1").css("visibility",(dom.size()==0)?"visible":"hidden");
dom = $("iframe#go2").contents().find(".oddRowTR");
$("#mask2").css("visibility",(dom.size()==0)?"visible":"hidden");
}

Interlude: I apologize that it takes me so long to write up this series of posts; however the need to meticulously sort though all the necessary snippets and screenshots is taking more time than I previously thought. Bear with me - there are "only" 3 posts left. In the meantime, the info board continues to work - for nearly 6 months already.