Basic Application Development for Apple TV (with TVML & TVJS) with API calling & dynamic TVML
In my first blog, I talked about how to create client-server based application in tvOS using TVML and TVJS. So, to get started, you can take reference from it.
Today, I am talking about how to send/retrieve information from remote server, so our mobile app will show us always updated results. To retrieve any information from server using TVML/TVJS, use TVJS XMLHttpRequest.
To retrieve any information or file from server, create a new XMLHttpRequest object.
var templateXHR = new XMLHttpRequest();
This object provides methods and properties to send requests, respond to events, and store the results of the request. So, XMLHttpRequest (XHR) is an API that can be used by many scripting languages such as JavaScript, TVJS, JScript, VBScript, etc. to transfer and manipulate XML data to and from a webserver using HTTP, establishing an independent connection channel between a webpage’s Client-Side and Server-Side.
open( method, URL )
open( method, URL, async )
open( method, URL, async, userName )
open( method, URL, async, userName, password )
To configure the XHR request, call the open method. It contains the method, URL , and other parameters of a request. Here, method and URL parameters are mandatory for each XHRRequest and other three parameters (async, userName, password) are optional. Method parameter can be of “GET”, “POST” “PUT” or “DELETE”. async parameters specifies whether request is asynchronous or not, It can have value of “true” or “false” for async and sync respectively and username/password parameters need to pass if your request needs a authentication.
setRequestHeader( label, value )
Adds a label/value pair to the HTTP header to be sent i.e it generally uses when your XHR request send some data in its header such as Content-type , X-Auth-Token etc.
send( content )
After the request object has been configured, use the send method to send the request.
XMLHttpRequest Properties
- onreadystatechange :
An event handler for an event that fires at every state change.
- readyState
The readyState property defines the current state of the XMLHttpRequest object.
The following table provides a list of the possible values for the readyState property:
State | Description |
0 | The request is not initialized. |
1 | The request has been set up. |
2 | The request has been sent. |
3 | The request is in process. |
4 | The request is completed. |
- responseText : Returns the response as a string.
- response: Returns the response as a object.
- responseXML: Returns the response as XML.
- status: Returns the status as a number (e.g., 404 for “Not Found” and 200 for “OK”).
- statusText: Returns the status as a string (e.g., “Not Found” or “OK”).
function callServerForURL(url) {
var xhr = new XMLHttpRequest();
xhr.open(“GET”, url, true);
xhr.send();
var response = JSON.parse(xhr.response);
console.log(response);
return response;
}
We create a function with name callServerForURL(This function is generic and can be used for every API request the application will make) in application.js class which takes url as input parameter and it retrieves response from specific url and after getting response it parse this response in JSON format and return that JSON.
Now, we create a GET function to get response from specific URL but how can we use that? How can we pass parameter in this function? Here your answers are:
App.onLaunch = function(options) {
var response = callServerForURL(“http://api.themoviedb.org/3/tv/popular?api_key=YourKeyHere”);
}
application.js includes App.onLaunch method in it and this method tells to the app that what it need to perform when app is launch. Let’s look at the API. To access the data we make a GET request to the following URL : ’http://api.themoviedb.org/3/tv/popular?api_key=‘ including the key at the end. Ensure to replace YourKeyHere with your api key. When you hit this url you will get result like this:
{
“results”: [{
“poster_path”: “\/jIhL6mlT7AblhbHJgEoiBIOUVl1.jpg”,
“id”: 1399,
“name”: “Game of Thrones”,
}, …
],
“total_results”: 19997,
“total_pages”: 1000
}
As you can see that it contains page number, array of results, number of total pages and total results. So, you have enough idea What exactly we need to show.
function showAPIResponse(response) {
var templateXML = ‘<?xml version=”1.0″ encoding=”UTF-8″ ?><document>’;
templateXML += ‘<searchTemplate>’;
templateXML += ‘<background><img src= “” width=”1920″ height=”1080″ /></background>’;
templateXML += ‘<collectionList>’;
templateXML += ‘<grid>’;
templateXML += ‘<header>’;
templateXML += ‘<description style=”color: rgba(255, 255, 255); font-size:36px;”>Show videos</description>’;
templateXML += ‘</header>’;
templateXML += ‘<section>’;
var results = response.results;
for (i in results) {
var imgUrl = “http://image.tmdb.org/t/p/w500″+ htmlSpecialChars(results[i].poster_path);
templateXML += ‘<lockup id=”‘ + results[i].id+ ‘”><img src=”‘ + imgUrl +'” width=”350″ height=”216″ /><title style=”color:rgba(255, 255, 255, 0.5);”>’ + htmlSpecialChars(results[i].name) + ‘</title><subtitle style=”color:rgba(255, 255, 255, 0.5);”> Votes ‘ + results[i].vote_count + ‘</subtitle></lockup>’;
}
templateXML += ‘</section>’;
templateXML += ‘</grid>’;
templateXML += ‘</collectionList>’;
templateXML += ‘</searchTemplate>’;
templateXML += ‘</document>’;
console.log(templateXML)
return templateXML;
}
var htmlSpecialChars = function(text) {
return text
.replace(/&/g, “&”)
.replace(/</g, “<”)
.replace(/>/g, “>”)
.replace(/”/g, “"”)
.replace(/’/g, “‘”);
}
showAPIResponse is a function that takes response as a input parameter and convert that response into Apple premade templates currently I am using searchTemplate from into Apple premade templates but instead of creating static template it will show you dynamic content. I have also created a htmlSpecialChars function because sometimes we get some special character from API response such as &, <, >, etc and this special characters need special attention otherwise your XML will be invalid so to resolve this issue, we need to replace that character in other format.
var getTemplate = showAPIResponse(response);
After getting response, we just pass API response to showAPIResponse method to create a dynamic search template and after creating dynamic templateXML you just need to parse this template string into application/xml format. So, to do this you have to add an additional code for it in your App.onLaunch() method.
var parser = new DOMParser();
var parseTemplate = parser.parseFromString(getTemplate, “application/xml”);
After parsing just need to push that parse document in navigation stack:
navigationDocument.pushDocument(parseTemplate);
So,your final code for App.onLaunch will be like:
App.onLaunch = function(options) {
var response = callServerForURL(“http://api.themoviedb.org/3/tv/popular?api_key=YourKeyHere”);
var getTemplate = showAPIResponse(response);
var parser = new DOMParser();
var parseTemplate = parser.parseFromString(getTemplate, “application/xml”);
navigationDocument.pushDocument(parseTemplate);
}
But before run app, you just need to start your SimpleHTTPServer server (if you don’t know about it, you can refer my first blog). Now, just run your app and you will see result like this:
Cheers!
I confirmed it’s don’t work, i think you should have sample project for download ?
Best, UT
I can’t get it to work, xcode is giving me an error referencing this line:
var response = callServerForURL(“http://api.themoviedb.org/3/tv/popular?api_key=(my api key”);
I have already verified the link works by pasting it in the browser (with an api key).
I have also tested my local http server by running the app with the hello world alert template before I started. Any ideas what I might be missing?
Here’s the full code of my application.js
App.onLaunch = function(options) {
var response = callServerForURL(“http://api.themoviedb.org/3/tv/popular?api_key=c059c288751da5f08e1b06ab7cfcf39e”);
var getTemplate = showAPIResponse(response);
var parser = new DOMParser();
var parseTemplate = parser.parseFromString(getTemplate, “application/xml”);
navigationDocument.pushDocument(parseTemplate);
}
App.onWillResignActive = function() {
}
App.onDidEnterBackground = function() {
}
App.onWillEnterForeground = function() {
}
App.onDidBecomeActive = function() {
}
App.onWillTerminate = function() {
}
/**
* This convenience funnction returns an alert template, which can be used to present errors to the user.
*/
var createAlert = function(title, description) {
var alertString = `
${title}
${description}
`
var parser = new DOMParser();
var alertDoc = parser.parseFromString(alertString, “application/xml”);
return alertDoc
}
var templateXHR = new XMLHttpRequest();
function callServerForURL(url) {
var xhr = new XMLHttpRequest();
xhr.open(“GET”, url, true);
xhr.send();
var response = JSON.parse(xhr.response);
console.log(response);
return response;
}
function showAPIResponse(response) {
var templateXML = ‘’;
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘Show videos’;
templateXML += ‘’;
templateXML += ‘’;
var results = response.results;
for (i in results) {
var imgUrl = “http://image.tmdb.org/t/p/w500″+ htmlSpecialChars(results[i].poster_path);
templateXML += ‘’ + htmlSpecialChars(results[i].name) + ‘ Votes ‘ + results[i].vote_count + ‘’;
}
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘’;
templateXML += ‘’;
console.log(templateXML)
return templateXML;
}
var htmlSpecialChars = function(text) {
return text
.replace(/&/g, “&”)
.replace(//g, “>”)
.replace(/”/g, “"”)
.replace(/’/g, “‘”);
}