Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
magnatronus committed Oct 11, 2015
1 parent 28c6912 commit b9d9b93
Show file tree
Hide file tree
Showing 31 changed files with 2,555 additions and 2 deletions.
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
# tvDemo
A NodeJS version of the AppleTV demo project
# tvDemo Introduction

**tvDemo** is a test project (written with NodeJS, ExpressJS and EJS) for the new Apple tvOS. This is actually a modification to the demo code that Apple uses to give an overview of the template functionality of tvOS. I thought the best way to get to grips with it was to try and using some other technolgies to create a duplicate app, and though now whwre near perfect, it does appear to work and I do lile the user of EJS to render the TVML templates.


## Prerequisites
Yo will need the latest (at the moment) beta of XCode 7.1 as well as this project if you want to run the code and see what it does (you don't need a physical developer kit to run the app as XCode has a simulator).

You should create a simple Swift tvOS app project (as explained in the developer docs) and then just set the following to connect to the server when it is running:

```
static let TVBaseURL = "http://localhost:9001/"
static let TVBootURL = "\(AppDelegate.TVBaseURL)js/application.js"
```

Setting tvBaselURL to wherever the Node server is running.

26 changes: 26 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* This is the main Node app.js
* This is the first file that is run when the server starts
*
* developed by Steve Rogers (@sarmcon)
* Oct 2015
*/

var express = require('express'),
app = express();

// set the view engine to ejs
app.set('view engine', 'ejs');
app.set('views', __dirname + '/views');


// set up our static file route
app.use(express.static(__dirname + '/public'));

// set up our routes
app.use(require('./routers/tvml'))

// Start our Server
var server = app.listen(9001, function () {
console.log('Node tvOS sample app now listening on port: %s', server.address().port);
});
35 changes: 35 additions & 0 deletions lib/storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* storage.js
* a quick and dirty helper module to store temp vars while the app is running
* should probably use somthing a bit better - but you get the idea!
*
* developed by Steve Rogers (@sarmcon)
* Oct 2015
*/
var store={};


/**
* Exported Functions
*/
exports.get = getData;
exports.set = saveData;


/**
* Save a value to our temp store
* @param key
* @param value
*/
function saveData(key, value){
store[key] = value;
}

/**
* Get a value to our temp store
* @param key
*/
function getData(key){
return store[key]||false;
}
15 changes: 15 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"name": "tott",
"version": "1.0.0",
"description": "A Test Node TV Server",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Steve Rogers",
"license": "ISC",
"dependencies": {
"ejs": "^2.3.4",
"express": "^4.13.3"
}
}
112 changes: 112 additions & 0 deletions public/js/Presenter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Presenter.js
*
* from Original Apple example code - modfied to use XMLHttpRequest loading in rResouceLoader
*
*/

var Presenter = {

// The default Presenter
defaultPresenter: function(xml) {
if(this.loadingIndicatorVisible) {
navigationDocument.replaceDocument(xml, this.loadingIndicator);
this.loadingIndicatorVisible = false;
} else {
navigationDocument.pushDocument(xml);
}
},

// modal presenter
modalDialogPresenter: function(xml) {
navigationDocument.presentModal(xml);
},

// menu bar presenter
menuBarItemPresenter: function(xml, ele) {

var feature = ele.parentNode.getFeature("MenuBarDocument");
if (feature) {
var currentDoc = feature.getDocument(ele);
if (!currentDoc) {
feature.setDocument(xml, ele);
}
}
},


// document load event handler
load: function(event) {
console.log(event);

var self = this,
ele = event.target,
templateURL = ele.getAttribute("template"),
presentation = ele.getAttribute("presentation");

if (templateURL) {

self.showLoadingIndicator(presentation);

// modified to use our XMLHttpRequest Loader
resourceLoader.loadResource(templateURL, function(doc) {

doc.addEventListener("select", self.load.bind(self));
doc.addEventListener("highlight", self.load.bind(self));

if (self[presentation] instanceof Function) {
self[presentation].call(self, doc, ele);
} else {
self.defaultPresenter.call(self, doc);
}

});
}
},

// generate doc from TVML code
makeDocument: function(resource) {
if (!Presenter.parser) {
Presenter.parser = new DOMParser();
}

var doc = Presenter.parser.parseFromString(resource, "application/xml");
return doc;
},


// display a loading indicator
showLoadingIndicator: function(presentation) {

if (!this.loadingIndicator) {
this.loadingIndicator = this.makeDocument(this.loadingTemplate);
}

if (!this.loadingIndicatorVisible && presentation != "modalDialogPresenter" && presentation != "menuBarItemPresenter") {
navigationDocument.pushDocument(this.loadingIndicator);
this.loadingIndicatorVisible = true;
}
},

// remove loading indicator
removeLoadingIndicator: function() {
if (this.loadingIndicatorVisible) {
navigationDocument.removeDocument(this.loadingIndicator);
this.loadingIndicatorVisible = false;
}
},

/**
* @description Instead of a loading a template from the server, it can stored in a property
* or variable for convenience. This is generally employed for templates that can be reused and
* aren't going to change often, like a loadingIndicator.
*/
loadingTemplate: `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<loadingTemplate>
<activityIndicator>
<text>Loading...</text>
</activityIndicator>
</loadingTemplate>
</document>`
}
34 changes: 34 additions & 0 deletions public/js/ResourceLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* ResourceLoader.js
*
* Helper to load our requested resource using XMLHttpRequest
*/


function ResourceLoader(baseurl) {
this.BASEURL = baseurl;
}


/**
* This will load the specified resource and then invoke the callback
* This version uses the XMLHttpRequest method as we do not need to parse the result before using
* as EJS does this for us.
*/
ResourceLoader.prototype.loadResource = function(url, callback) {

var self = this;

var templateXHR = new XMLHttpRequest();
templateXHR.responseType = "document";
templateXHR.addEventListener("load", function() {
callback.call(self,templateXHR.responseXML);
}, false);

templateXHR.open("GET", `${self.BASEURL}` + url, true);
templateXHR.send();
return templateXHR;

}


58 changes: 58 additions & 0 deletions public/js/application.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* application.js
*
* This is the entry point to the application and handles the initial loading of required JavaScript files.
*
* developed by Steve Rogers (@sarmcon)
* Oct 2015
*/
var resourceLoader;

/**
* Run when the App launches
*/
App.onLaunch = function(options){

var javascriptFiles = [
`${options.BASEURL}js/ResourceLoader.js`,
`${options.BASEURL}js/Presenter.js`
];

evaluateScripts(javascriptFiles, function(success) {

if(success){
resourceLoader = new ResourceLoader(options.BASEURL);
resourceLoader.loadResource("", function(doc) {
doc.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(doc);
});
} else {
var alert = createAlert("Evaluate Scripts Error", "There was an error attempting to evaluate the external JavaScript files.\n\n Please check your network connection and try again later.");
navigationDocument.presentModal(alert);

throw ("Playback Example: unable to evaluate scripts.");
}

});

}

/**
* This convenience function returns an alert template, which can be used to present errors to the user.
*/
var createAlert = function(title, description) {

var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
<document>
<alertTemplate>
<title>${title}</title>
<description>${description}</description>
</alertTemplate>
</document>`

var parser = new DOMParser();

var alertDoc = parser.parseFromString(alertString, "application/xml");

return alertDoc
}
Loading

0 comments on commit b9d9b93

Please sign in to comment.