Showing posts with label html. Show all posts
Showing posts with label html. Show all posts

Saturday, 7 October 2017

Quote API with node JS

As Winston Churchill once said: "It is a good thing for an uneducated man to read books of quotations."

Having almost completed all the content I want for my dashboard I decided to add a little more to the title which currently just says "Gally Home Dashboard". I remember back at my old Secondary School - de Ferrers Specialist Technology College we used to have an intranet that gave a "thought of the day." So I started looking at how I can add quotes to my dashboard.
Boring Title Page
Option 1: Create a JSON file that contains quotations I like.
var quotes= [{
 "name": "Winston Churchill",
 "quote": "It is a good thing for an uneducated man to read books of quotations."
},
...];
We can then access these quotes by accessing a random part of the array.
getQuote(randNo){
  var thisQuote = JSON.parse(quotes[randNo]);
  var returnString = thisQuote.name + ": " + thisQuote.quote;
  return returnString;
}
There are two downsides to this. 1. I'd have to populate a huge JSON 2. It would be full of quotes I already know and would leave me in a "quote bubble". Not very educational to the uneducated man.

Option 2: Don't reinvent the wheel. Use an API.
There are several APIs we can use;
 * Quotes on Design - This allows us to decide a filter on the order and the number of results.
JS:
$.getJSON("https://quotesondesign.com/wp-json/posts?filter[orderby]=rand&filter[posts_per_page]=1&callback=", function(a) {
  $("body").append(a[0].content + "<p>&mdash; " + a[0].title + "</p>")
});
Example from the webpage
Curl:
curl -X GET "https://quotesondesign.com/wp-json/posts?filter=rand&filter=1"
* Forismatic - Similar to quotes on design but also includes "expressions". Neither appears to have rate limits or need an authentication code to be used!
Results can come in
Curl:
curl -X GET "https://api.forismatic.com/api/1.0/?method=getQuote&key=&format=json&lang=en
The language can be set to english or russian (?)
key can be set to blank which gives a random quote.
response format comes in: json, jsonp, xml, html and plain text.

Output for english, random key and different formats:
html: 
<blockquote><strong><cite>We are what we repeatedly do. Excellence, then, is not an act, but a habit. </cite></strong><br/><small>Aristotle <address></address></small><blockquote>
json:
{"quoteText":"We know the truth, not only by the reason, but by the heart. ", "quoteAuthor":"Blaise Pascal", "senderName":"", "senderLink":"", "quoteLink":"http://forismatic.com/en/b917914ba7/"}
text:
You do not become good by trying to be good, but by finding the goodness that is already within you. (Eckhart Tolle) 
The second comes with a bit more freedom. In the end I made it match how I was doing requests throughout the rest of the dashboard (please see my other posts) and then self formatting to how I wanted it to look on the client side.

My Basic Flow:
 - Use request npm to match the curl command above (using json)
 - Send the json response to the client
 - Parse the json and format the output in the client JS
document.getElementById("randomQuote").innerHTML = quote.quoteAuthor + ": " + quote.quoteText; 
 - Some responses had no author, so we attribute them to "anon"
 if (quote.quoteAuthor === "") {
   quote.quoteAuthor = "Anon";
}

To what I believe are great results!


Any thoughts? Feel free to comment below. 

Tuesday, 19 September 2017

National Rail Enquiries Darwin Lite Webservice and Node JS

Getting train times for my local dashboard

Recently I decided I wanted to make a dashboard using Node JS that would provide quick information that I look for regularly. These might be;
  - Share prices I'm interested in
  - Latest football scores and upcoming matches for my football team (Burton Albion)
  - A sub section of my twitter feed

What I definitely wanted was to be able to click onto my dashboard and quickly find out the next trains I can get back to Winchester as I've been commuting to and from London recently and leave for home at differing times. There can occasionally be delays and I'd like to see if there's any delays so I can leave early or later to avoid a rush.

So where to look? The obvious place to try was a quick google of network rail api's which took me to: http://www.nationalrail.co.uk/46391.aspx

The three API options were:
Online Journey Planner - This deals more with planning routes, calculating ticket prices etc. Which since I follow the same route everyday and buy a daily travel card, it doesn't really fit my need.

Knowledgebase - This provides information on engineer disruption, station data such as facilities and service disruption. Whilst the latter is helpful the others were less so, as my stations are always the same and I would be aware of engineer work on the journey to London.

Darwin - In its own words "GB rail industry’s official train running information engine, providing real-time arrival and departure predictions, platform numbers, delay estimates, schedule changes and cancellations." which fits my requirements perfectly!

To use the Darwin SOAP API's you need an authentication token which you get by registering here:

This limits you to 5000 requests / hour which is around 5 million over 28 days. This cap can be removed by contacting infoservices@nationalrail.co.uk though it's extremely unlikely I will hit that!

You can find all the documentation for the API's here:

*NOTE*: You must follow the brand guidelines if you are using the APIs attributing the data to network rail. Brand Guidelines & Logos

So how did I use it?

Great Question!
Now I've not used SOAP much so did the obvious thing and imported the supplied WSDL (v2017-02-02) (https://lite.realtime.nationalrail.co.uk/OpenLDBWS/wsdl.aspx?ver=2017-02-02) into SOAPUI (v5.3.0).

This gave me all the services I can use as well as example XML formats for input to a REST API.



This gives me the HTTP address to make the calls to, the valid XML the service requires and lets me test with the data I will be using.
       <typ:TokenValue> : This is supplied by Network Rail after registration and is required
      <ldb:numRows> : This denotes the number of responses to return.
       <ldb:crs>  : The first of these which is required states the departure station, in my case Waterloo  
        or WAT in network rail world. Full list: http://www.nationalrail.co.uk/stations_destinations/default.aspx 
                          : The second is used for destination station and more can be added for other 
       destination.
      <ldb:timeOffset> : This is how much after the current time you want the search to look for.
      <ldb:timeWindow> : Denotes amount of time to get results for

The output is also very straight forward based on SOAP UI:

Making the request in Node JS

In our node JS application we can achieve the equivalent of this call by using the "request" node module: https://www.npmjs.com/package/request

var request = require("request"); 
var postOptions = {
      url: 'https://lite.realtime.nationalrail.co.uk/OpenLDBWS/ldb10.asmx',
      headers: {
        'Content-Type': 'text/xml'
      },
      body: data/waterlooToWinchester.xml,
      method: 'POST'
    };
    request(postOptions, function cb(err, res, body) {
      if(err) {
        console.error(err);
      } else { 
console.log(body);
}  
Where "data/waterlooToWinchester.xml" is the same XML shown in the SOAPUI request example.

This outputs:
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><GetDepBoardWithDetailsResponse xmlns="http://thalesgroup.com/RTTI/2017-02-02/ldb/"><GetStationBoardResult xmlns:lt6="http://thalesgroup.com/RTTI/2017-02-02/ldb/types" xmlns:lt="http://thalesgroup.com/RTTI/2012-01-13/ldb/types" xmlns:lt4="http://thalesgroup.com/RTTI/2015-11-27/ldb/types" xmlns:lt5="http://thalesgroup.com/RTTI/2016-02-16/ldb/types" xmlns:lt2="http://thalesgroup.com/RTTI/2014-02-20/ldb/types" xmlns:lt3="http://thalesgroup.com/RTTI/2015-05-14/ldb/types"><lt4:generatedAt>2017-09-19T22:44:55.6600574+01:00</lt4:generatedAt><lt4:locationName>London Waterloo</lt4:locationName><lt4:crs>WAT</lt4:crs><lt4:filterLocationName>Winchester</lt4:filterLocationName><lt4:filtercrs>WIN</lt4:filtercrs><lt4:platformAvailable>true</lt4:platformAvailable><lt6:trainServices><lt6:service><lt4:std>23:05</lt4:std><lt4:etd>On time</lt4:etd><lt4:operator>South Western Railway</lt4:operator><lt4:operatorCode>SW</lt4:operatorCode><lt4:serviceType>train</lt4:serviceType><lt4:length>5</lt4:length><lt4:serviceID>0rdRe8ywmDifl5VO1N1Q4Q==</lt4:serviceID><lt5:rsid>SW927700</lt5:rsid><lt5:origin><lt4:location><lt4:locationName>London Waterloo</lt4:locationName><lt4:crs>WAT</lt4:crs></lt4:location></lt5:origin><lt5:destination><lt4:location><lt4:locationName>Poole</lt4:locationName><lt4:crs>POO</lt4:crs></lt4:location></lt5:destination><lt6:subsequentCallingPoints><lt6:callingPointList><lt4:callingPoint><lt4:locationName>Woking</lt4:locationName><lt4:crs>WOK</lt4:crs><lt4:st>23:31</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Basingstoke</lt4:locationName><lt4:crs>BSK</lt4:crs><lt4:st>23:51</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Micheldever</lt4:locationName><lt4:crs>MIC</lt4:crs><lt4:st>00:03</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Winchester</lt4:locationName><lt4:crs>WIN</lt4:crs><lt4:st>00:11</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Eastleigh</lt4:locationName><lt4:crs>ESL</lt4:crs><lt4:st>00:22</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Southampton Airport Parkway</lt4:locationName><lt4:crs>SOA</lt4:crs><lt4:st>00:27</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>St Denys</lt4:locationName><lt4:crs>SDN</lt4:crs><lt4:st>00:32</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Southampton Central</lt4:locationName><lt4:crs>SOU</lt4:crs><lt4:st>00:37</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Totton</lt4:locationName><lt4:crs>TTN</lt4:crs><lt4:st>00:43</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Brockenhurst</lt4:locationName><lt4:crs>BCU</lt4:crs><lt4:st>00:54</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>New Milton</lt4:locationName><lt4:crs>NWM</lt4:crs><lt4:st>01:02</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Christchurch</lt4:locationName><lt4:crs>CHR</lt4:crs><lt4:st>01:09</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Pokesdown</lt4:locationName><lt4:crs>POK</lt4:crs><lt4:st>01:13</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Bournemouth</lt4:locationName><lt4:crs>BMH</lt4:crs><lt4:st>01:17</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Branksome</lt4:locationName><lt4:crs>BSM</lt4:crs><lt4:st>01:23</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Parkstone (Dorset)</lt4:locationName><lt4:crs>PKS</lt4:crs><lt4:st>01:26</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint><lt4:callingPoint><lt4:locationName>Poole</lt4:locationName><lt4:crs>POO</lt4:crs><lt4:st>01:30</lt4:st><lt4:et>On time</lt4:et><lt4:length>5</lt4:length></lt4:callingPoint></lt6:callingPointList></lt6:subsequentCallingPoints></lt6:service></lt6:trainServices></GetStationBoardResult></GetDepBoardWithDetailsResponse></soap:Body></soap:Envelope>
This isn't much good to me at least, as I prefer to use JSON to access the data at the web client side. To do this there is a neat little node module called "xml2js": https://www.npmjs.com/package/xml2js

The below snippet parses the xml string and removes the prefixes from the xml tags to make it more readable and accessible as JSON.

var parseString = require('xml2js').parseString;
var stripPrefix = require('xml2js').processors.stripPrefix;
parseString(body, { tagNameProcessors: [stripPrefix] }, function (err, result) {
            if(err){
              console.log(err);
            }else{
              var currentJourney = result.Envelope.Body[0].GetDepBoardWithDetailsResponse[0].GetStationBoardResult[0].trainServices[0].service[0];
                  var train = {
                    "trainTime": currentJourney.std[0],
                    "status": currentJourney.etd[0],
                    "departingFrom": currentJourney.origin[0].location[0].locationName[0],
                    "goingTo": currentJourney.destination[0].location[0].locationName[0]
                  };
}, function afterJourneysProcessed(err) {
                if(err) {
                  console.log(err);
                }else{
                  return(train);
                }
});
At the client side we can handle this "train" variable by parsing the JSON and then setting the html based on the JSON input.

dashboard.html
<p id="nexttrains1"></p>
dashboard.js

var example = JSON.parse(this.responseText);
        //train 1
        var trainOutput1 = example[0].departingFrom + " to " + example[0].goingTo + "<br>";
        trainOutput1 = trainOutput1 + "<b>Departing at:</b> " + example[0].trainTime + " <b>Current Status:</b> " + example[0].status;
        document.getElementById("nexttrains1").innerHTML = trainOutput1;
This returns a single train journey. In my own code I also included an array called "calling points" and returned five journeys. This is quite a bit more code and a little too much for a single blog (since I've already added a lot already) but I can pass it over on request. Email me if you'd like to see the full code with promises and client to server code!

What does it look like? 


Anything you would do differently?
Any questions?

Feel free to email or comment and I'll gladly get back to you!

Friday, 27 January 2017

HTML Image Arrays

I recently started to play with php and building a website. What I really wanted to do was to create a checkbox list of a certain category. Place names and the like. But it was a lot of repetitive typing, so I toyed with writing an array. 

The array was fairly simple to create but didn't look very nice. So I instead decided that images would better represent whatever it was I was displaying.
My first play (getting bits and bobs off the internet) looked something like this;

Array of images loaded side by side
<html>
<body onload="buildImage('slct1');">
<script>
   function buildImage(slct1) {
       
        var images = ["a.jpg","b.jpg","c.jpg","d.jpg","e.jpg"];
                 var s2 = document.getElementById(slct1);
                 s2.innerHTML = "";
                 var optionArray = ["a.jpg","b.jpg","c.jpg","d.jpg","e.jpg"];
                 for (var option in optionArray) {
                     if (optionArray.hasOwnProperty(option)) {
                         var pair = optionArray[option];
                         var img = document.createElement("img");
                         img.src = optionArray[option];
                         img.height = 410;
                         img.width = 327;
                         s2.appendChild(img);
                        
                         var label = document.createElement('label')
                         label.htmlFor = pair;
                         label.appendChild(document.createTextNode(pair));
                     }
                 }
      }
</script>
</body>
</html>
********************
Lesson 1: Make sure you're using " instead of 
      ********************
For the sake of playing. I was then able to put them vertical by adding the simple line:
            s2.appendChild(document.createElement("br"));
on the last line of the if statement within the function.
                         ...
                         var label = document.createElement('label')
                         label.htmlFor = pair;
                         label.appendChild(document.createTextNode(pair));
                         s2.appendChild(document.createElement("br"));
                     }...
Array of images loaded vertically
********************
Lesson 2: There is no need to over complicate what can be simple.
      ********************
All is going well. Though I'd prefer to allow the user to cycle through the images. Again using an array so as to keep reduce the amount of manual labour I need to do to expand the website.

The next step is to load the first image in a single space and then add buttons to allow you to move from the previous image to the next image.


The above images show me cycling through the image letters until we get to "F" which isn't in the images array so the unloaded image icon appears where an image should be.

<html>
<body onload="buildImage();">
<div class="contents" id="content" align="center">
<button onclick="previousImage()">Previous Image</button><button onclick="nextImage()">Next Image</button><br>
</div>
<script>
    var images = ["a.jpg","b.jpg","c.jpg","d.jpg","e.jpg"];
    var index = 0;

    function buildImage() {
      var img = document.createElement('img')
      img.src = images[index];
      img.height = 410;
      img.width = 327;
      document.getElementById('content').appendChild(img);
    }
   function nextImage(){
      var img = document.getElementById('content').getElementsByTagName('img')[0]
      index++;
      img.src = images[index];
      img.height = 410;
      img.width = 327;
}
    function previousImage(){
      var img = document.getElementById('content').getElementsByTagName('img')[0]
      index--;
      img.src = images[index];
      img.height = 410;
      img.width = 327;
    }
</script>
</body>
</html>

This is solved by adding the following code: index = index % images.length;
This loops the images forever and ever and ever and ever and ever and ever.
********************
Lesson 3: Don't test this functionality with 100 images.
      ********************
A final thought.. you can implement other functions to be attached "on click" to these images as they load. In the image properties just add: img.onclick = function(){FUNCTION(param1, param2)};

So happy coding everyone and many thanks to the following:
http://www.w3schools.com
http://stackoverflow.com/questions/13330202/how-to-create-list-of-checkboxes-dynamically-with-javascript