Showing posts with label CSS. Show all posts
Showing posts with label CSS. Show all posts

Monday, August 6, 2018

Old Phone/Tablet as an Info Board: Table of Contents

Hi folks, now that this long overdue series of posts is complete, here's a table of contents for the ease of the reading.



Enjoy!

TL/DR: This is a series of (moderately boring) posts about how to turn your old and outdated tablet or smartphone into an information board you can use at home. Uses range from purely practical to purely aesthetic.

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.

Thursday, January 18, 2018

Old Phone/Tablet as an Info Board Part 1: IFRAME's and their limitations

This continues our series on making an info screen. Last time, we created a skeleton layout, so let us begin filling it with contents.

As an example, let us try to display current and hourly weather using this webpage:
https://www.theweathernetwork.com/ca/hourly-weather-forecast/ontario/mississauga

Highlighted are regions I'd like to put on the info board.

The most straightforward way of putting something from the third-party website into your own would be an IFRAME tag, of this global format:
<IFRAME scrolling="no" src="..." style="..."></IFRAME>

Now usually you would only need some portion of the website displayed on your screen. Unfortunately, if the contents of your IFRAME is from the third-party website, you cannot interact with its content (with a very few exceptions) due to the commonly accepted same-origin policy. (Annoying as it is in our case, this limitation is what prevents a fair amount of malicious attacks.)

However, the desired portion of the website can be extracted via re-positioning the iframe using this negative margin trick:

style="margin-left: -(XXX)px; margin-top: -(YYY)px;"

To zoom in or out on the corresponding website, the only way is to use CSS transform property, like so:

<div id="weather1" class="basic" style="position: relative; left: 5px; top: 10px; width: 500px; height: 180px;">
<iframe scrolling="no" src="https://www.theweathernetwork.com/ca/hourly-weather-forecast/ontario/mississauga" 
style=" -webkit-transform: scale(0.72);  -webkit-transform-origin: 0 0;
 transform: scale(0.72);  transform-origin: 0 0; 
        margin-left: -20px; margin-top: -200px; 
 border: 0px none; height: 8120px; width: 750px;"> 
</iframe></div>

<div id="weather2" class="basic" style="position: relative; left: 5px; top: 20px; width: 500px; height: 200px;">
<iframe scrolling="no" src="https://www.theweathernetwork.com/ca/hourly-weather-forecast/ontario/mississauga" 
style=" -webkit-transform: scale(0.72);  -webkit-transform-origin: 0 0;
 transform: scale(0.72);  transform-origin: 0 0; 
        margin-left: -30px; margin-top: -690px; 
 border: 0px none; height: 8120px; width: 750px;"> 
</iframe></div>
(The -webkit- prefix is needed for browsers like the PlayBook's (or Safari); some other browsers may need other prefixes.)
This gives us something like:


As you can see, this method is very easy to code and there is no need (and no possibility for that matter) to do any rearrangement of the displayed information. The target website takes care of that for you.


Two major pitfalls (besides the above mentioned inability to interact with the iframe contents) are:
  1. Manual adjustment of the margin is very unreliable. Granted, other methods are prone to failing once the target web site undergoes a redesign, but here even a minor change of the layout would screw the placement of your desired content and require re-adjustment. What is worse, this may happen even without a website redesign proper, due to some external content (e.g. ads) affecting the elements' location and sizing. So your screen can intermittently show the wrong content, and may need frequent readjustments.
  2. Even though most of the iframe's target website will be hidden, it will still be loaded and processed (all its scripts, embedded videos, plugins, and ads included), which makes it very heavy on the client browser (especially on older hardware such as the PlayBook). Using transformation makes matters much worse (I guess it may even force the browser to have to "invisibly" render the entire page - how else would the browser know how to scale it?). In my testing, the above example rendered my PlayBook rather unresponsive. 
So, this method would be prohibitively slow for many practically relevant cases. Still, it is a simple and viable method so long as the target URL is rather lightweight and not too loaded with dynamic HTML or embedded media. A good candidate is a mobile webpage or a page specially designed to be embedded, like this: 

<div id="weather1" class="basic" style="position: relative; left: 10px; top: 10px; width: 300; height: 185px; background: white;">
<iframe scrolling="no" src="http://weather.gc.ca/wxlink/wxlink.html?cityCode=on-24&lang=e" allowtransparency="true" 
 style="border: 0px none; height: 185px; width: 300px;"></iframe>
</div>



Friday, January 12, 2018

Old Phone/Tablet as an Info Board Intro: Backstory and Basics

Some time in the past, my loving wife gave me a Blackberry PlayBook for my birthday. As I don't use it much these days (my smartphone has become more versatile and powerful), and would loathe to part with it (it is barely worn and beautiful, and has sentimental value), I would like to give it a second life. 

So in a series of posts I am going to log how I turn the Playbook into an info board: an always-on, always up to date information screen next to the front door, showing some information such as the status of my commute and today's weather. 

Why an information screen in favor of other alternatives? For the same reasons they use departure boards at airports and transit stations: it is the fastest and the least disruptive way to get the important information. You can use it with your hands full (unlike your phone), and you don't have to stand there listening for a robotic voice (unlike Hey Google / Alexa / Siri).

Like so: (and yes, this is a sneak peek into the beta version of the end result):



The DiskStation server I have at home.
Rather than getting the SDK and writing an app (long!!!), I decided to make a web page and host it on my DiskStation (a network attached storage which has a web server function). This approach is much more versatile since it is not limited to the PlayBook or Blackberry. In fact, there will be very few PlayBook-specific points here (mostly limitations: PlayBook uses a rather old implementation of Webkit, and its processor is, by the modern web standards, not the fastest).

On the contrary, the procedures given here should be helpful for many other devices - old iPads, old Android tables, old smartphones (even though a smartphone has a smaller screen and there will be less info that it can meaningfully display) and perhaps something even more exotic like old monitors hooked to something like a Raspberry Pi.

In fact, when the screen is ready, anyone at home can access it from their desktop or phone if they need the info but don't feel like physically going downstairs.


So, to get the job done, we will be combining server-side programming (PHP) and client-side programming (JavaScript / jQuery).

Let's get started by designing an interface:

<html>
<head>
    <title>Info Screen</title>
  <style>
   div.background {background: black; fallback: linear-gradient(180deg, rgb(0,0,0), rgb(25,25,25)); position: absolute; left:0; top:0; height: 560px; width: 1024px; }
   div.basic { background-color: white; border: 1px solid rgb(255,255,255); border-radius: 15px; overflow: hidden; padding: 5px;}
   div.saver {filter:invert(100%);-webkit-filter:invert(100%);}
   div.info { text-align: center; vertical-align: middle;  font-family: "Arial", Helvetica, Sans-Serif; font-size:22px}
  .element {font-size: 12px; text-align: center;}
  .large{background-color: #ffffee;width:650px;height:400px;float:left;}
  </style>
</head>

<body> <div class="background">
<div id="status" class="basic info" style="position: absolute; left: 10px; top: 10px; width: 120px; height: 90px; background: #EEFFF8; color: gray; font-size: 12px;"></div>

<div id="weather" class="basic" style="position: absolute; left: 10px; bottom: 190px; width: 120px; height: 240px; background: #EEEEFF;"></div>
<div id="hourly" class="basic info" style="position: absolute; left: 10px; bottom: 10px; width: 990px; height: 160px; background: white;"></div>

<div id="map" class="basic" style="border: none; position: absolute; right: 10px; top: 10px; width: 415px; height: 350px;"></div>
<div id="travel" class="basic info" style="position: absolute; left: 150px; bottom: 190px; width: 415px; height: 60px; background:rgb(255,255,225);"> </div>

<div id="bus" class="basic info" style="position: absolute; left: 150px; bottom: 270px; width: 415px; height: 50px; background:rgb(255,235,235);"> </div>
<div id="train1" class="basic" style="position: absolute; left: 150px; top: 10px; width: 200px; height: 195px; "></div>
<div id="train2" class="basic" style="position: absolute; left: 365px; top: 10px; width: 200px; height: 195px; "></div>

</div>
</body> </html>

Note that the code above describes only the layout of the elements. In the upcoming posts (there will be 3-4 of them) I am going to discuss in detail how to actually fill the interface with contents, as well as how to script its automatic updates.

(On a side note, it looks like we've lived through a shift of paradigm about what good stuff is.

For centuries - and indeed, persisting all the way into the end of the 20th century - the basic idea of all goods was "the good stuff is the stuff that lasts".

Indeed, in a world where changes are slow and manufacturing is scarce, or expensive, or both, this made total sense. Having to replace anything (from your tools of the trade to the pair of shoes you wear) was an extra burden to be avoided if at all possible; ideally, good stuff would last a lifetime and sometimes even outlast its owners.

But nowadays - at least the "modernized countries" world - this has changed. And the biggest change has been the change of pace. Stuff improves much faster than it wears out. Not only that, but also - with the digital stuff in particular - your device is only as good as the software it works with, and if that software outruns your hardware, bad luck.

To summarize this in a metaphor, we became kids again - the digital kids who outgrow their digital clothes before they have any chance of wearing out. We may happen to have the best smartphone in the world today. But in a few years it will be bugged with so many "apps-that-won't update" and "sites-that-take-forever-to-load" and "services-that-no-longer-work" that using this once-perfect gadget would feel like dressing your five-year-old in clothes that were her favorite when she was three.

Friday, August 7, 2015

Why most WYSIWYG composers (and editors) suck

Whenever you need to write something that contains anything more complicated than plain text, you usually have a WYSIWYG-style editor at your disposal, be it authoring a simple cover letter in MS Word or using post composer on this blog.

WYSIWYG stands for "what you see is what you get" and means that you have simple text-styling tools (bold, italic, font size/color, etc.) at your disposal. It is usually a good thing to have - not because what you see in the composer is what you finally get (this is, generally speaking, false: your blog post will be affected by your template, the viewer's browser and many other factors; even in the telltale Word getting things exactly as you want them is not always easy), but because it is a time saver: it is often faster to press a toolbar button than to manually write HTML tags every time you need them.


But WYSIWYG has a huge caveat in that it encourages users to omit the most important step in text formatting: logic. Usually, any text can be broken down into logical blocks: here is the "main text", these sentences are "more important" or "less important", here is a "heading", this here is a "footnote", this is a "caption", and so on. And it is these logical blocks that we "decorate": make their text smaller / larger, bold/italic/underlined, set a different color / background, etc.

You've probably already guessed what I am getting at: styles. Now the (disappointing) truth is that any document the slightest bit more complicated than your garage sale ad will need styles. Unless you're writing something like a cyber-punk hippie gothic new age underground pamphlet, you'll want your logic blocks look the same throughout your document -- or many documents in case of a blog.

The caveat about WYSIWYG composers is that they direct your attention away from styles and towards text decoration. Most of you would still have an "implicit style table" in your memory, and would try to stick to it. This might work -- for a fraction of people who really deserve to have "paranoid attention to detail" in their résumés. The rest of us will inevitably have different  look to their logically identical fragments -- ranging from slightly different to markedly different and all the way to "you must have been daydreaming as you were writing this" different. 

And that's not the worst of it. Suppose you wrote half your big document (say a Ph.D. thesis in maths) and the formatting requirements were updated, urging you to change the font and coloring of all lemmas in your text (yes, all 118 of them). Or you wrote a bunch of blog posts and discovered that your quotations look ugly as they are, black on yellow centered, and should instead be green on off-white beige and left-aligned. If you had used your styles correctly, the change would be a few minutes. Otherwise, you're in for a night of miserable and thankless drudgery. 

This is why the first thing I did before I even started the blog was adding this to the template:

<!-- CUSTOM STYLES FOR CODE ITEMS-->
<style>
  .mycode {font-family:"Consolas", "Monaco", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace; 
           background-color:#E8E8E8;}
  .myaux {font-size:80%; color:#666699;background-color:#FFFFF0}
  .myinline {color:#999999;}
</style>
saving myself a whole lot of work in the future. Yes, I need to set the custom classes in the HTML mode, but it is way better than having to set all attributes manually (and rely on my less-than-ideal memory) each time I use them - not to mention that I need to go to the HTML mode to enter the code snippets anyway.