index.js

// external libraries
import 'babel-polyfill';
import _ from 'lodash';
import PubSub from 'pubsub-js';
import LZString from 'lz-string';

// internal libraries
import Parser from './parser';
import Ajax from './ajax';
import Page from './page';
import Navigation from './navigation';
import Handler from './handler';
import Settings from './settings';
import Menu from './menu';

// all supported configurations for each of the libraries
const configMap = {
	Ajax: [],
	Parser: [],
	Page: ['style'],
	Navigation: ['menu', 'templates'],
	Handler: ['handlers'],
};

// indicate whether the application was started
let started = false;

// all libraries
let libs = {
    /**
     * Internal alias to [lodash]{@link https://github.com/lodash/lodash} library
     * @alias module:ATV._
     */
    _: _,
    /**
     * Internal alias to [lz-string compression]{@link https://github.com/pieroxy/lz-string/} library
     * @alias module:ATV.LZString
     */
    LZString: LZString,
    /**
     * Ajax wrapper using Promises
     * @alias module:ATV.Ajax
     * @type {module:ajax}
     */
    Ajax: Ajax,
    /**
     * Page level navigation methods.
     * @alias module:ATV.Navigation
     * @type {module:navigation}
     */
    Navigation: Navigation,
    /**
     * Page Creation
     * @alias module:ATV.Page
     * @type {module:page}
     */
    Page: Page,
    /**
     * A minimalistic parser wrapper using the builtin DOMParser
     * @alias module:ATV.Parser
     * @type {module:parser}
     */
    Parser: Parser,
    /**
     * Basic event handling including some default ones
     * @alias module:ATV.Handler
     * @type {module:handler}
     */
    Handler: Handler,
    /**
     * Apple TV settings object with some basic helpers
     * @alias module:ATV.Settings
     * @type {module:settings}
     */
    Settings: Settings,
    /**
     * TVML menu template creation with few utility methods
     * @alias module:ATV.Menu
     * @type {module:menu}
     */
    Menu: Menu
};

/**
 * Iterates over each libraries and call setOptions with the relevant options.
 *
 * @private
 *
 * @param  {Object} cfg 	All configuration options relevant to the libraries
 */
function initLibraries(cfg = {}) {
	_.each(configMap, (keys, libName) => {
		let lib = libs[libName];
		let options = {};
		_.each(keys, (key) => options[key] = cfg[key]);
		lib.setOptions && lib.setOptions(options);
	});
}

// all supported Apple TV App level handlers
const handlers = {
    /**
     * App launch event
     *
     * @event onLaunch
     * @alias module:ATV#onLaunch
     */
    onLaunch(options = {}, fn) {
        libs.launchOptions = options;
        console.log('launching application...');
        fn(options);
    },
    /**
     * App error event
     *
     * @event onError
     * @alias module:ATV#onError
     */
    onError(options = {}, fn) {
        console.log('an error occurred in the application...')
        fn(options);
    },
    /**
     * App resume event
     *
     * @event onResume
     * @alias module:ATV#onResume
     */
    onResume(options = {}, fn) {
        console.log('resuming application...');
        fn(options);
    },
    /**
     * App suspend event
     *
     * @event onSuspend
     * @alias module:ATV#onSuspend
     */
    onSuspend(options = {}, fn) {
        console.log('suspending application...');
        fn(options);
    },
    /**
     * App exit event
     *
     * @event onExit
     * @alias module:ATV#onExit
     */
    onExit(options = {}, fn) {
        console.log('exiting application...');
        fn(options);
    },
    /**
     * App reload event
     *
     * @event onReload
     * @alias module:ATV#onReload
     */
    onReload(options = {}, fn) {
        console.log('reloading application...');
        fn(options);
    }
};

/**
 * Iterates over each supported handler types and attach it on the Apple TV App object.
 *
 * @private
 *
 * @param  {Object} cfg 	All configuration options relevant to the App.
 */
function initAppHandlers (cfg = {}) {
	_.each(handlers, (handler, name) => App[name] = _.partial(handler, _, (_.isFunction(cfg[name])) ? cfg[name] : _.noop));
}

/**
 * Starts the Apple TV application after applying the relevant configuration options
 *
 * @example
 * // create your pages
 * let SearchPage = ATV.Page.create({ page configurations });
 * let HomePage = ATV.Page.create({ page configurations });
 * let MoviesPage = ATV.Page.create({ page configurations });
 * let TVShowsPage = ATV.Page.create({ page configurations });
 * let LoginPage = ATV.Page.create({ page configurations });
 *
 * // template functions
 * const loaderTpl = (data) => `<document>
 *     <loadingTemplate>
 *         <activityIndicator>
 *             <title>${data.message}</title>
 *         </activityIndicator>
 *     </loadingTemplate>
 * </document>`;
 *
 * const errorTpl = (data) => `<document>
 *     <descriptiveAlertTemplate>
 *           <title>${data.title}</title>
 *           <description>${data.message}</description>
 *       </descriptiveAlertTemplate>
 *   </document>`;
 *
 * // Global TVML styles
 * let globalStyles = `
 * .text-bold {
 *     font-weight: bold;
 * }
 * .text-white {
 *     color: rgb(255, 255, 255);
 * }
 * .dark-background-color {
 *     background-color: #091a2a;
 * }
 * .button {
 *     background-color: rgba(0, 0, 0, 0.1);
 *     tv-tint-color: rgba(0, 0, 0, 0.1);
 * }
 * `;
 *
 * // start your application by passing configurations
 * ATV.start({
 *     style: globalStyles,
 *     menu: {
 *         attributes: {},
 *         items: [{
 *             id: 'search',
 *             name: 'Search',
 *             page: SearchPage
 *         }, {
 *             id: 'homepage',
 *             name: 'Home',
 *             page: HomePage,
 *             attributes: {
 *                 autoHighlight: true // auto highlight on navigate
 *             }
 *         }, {
 *             id: 'movies',
 *             name: 'Movies',
 *             page: MoviesPage
 *         }, {
 *             id: 'tvshows',
 *             name: 'TV Shows',
 *             page: TVShowsPage
 *         }]
 *     },
 *     templates: {
 *         // loader template
 *         loader: loaderTpl,
 *         // global error template
 *         error: errorTpl,
 *         // xhr status based error messages
 *         status: {
 *             '404': () => errorTpl({
 *                 title: '404',
 *                 message: 'The given page was not found'
 *             }),
 *             '500': () => errorTpl({
 *                 title: '500',
 *                 message: 'An unknown error occurred, please try again later!'
 *             })
 *         }
 *     },
 *     // global event handlers that will be called for each of the pages
 *     handlers: {
 *         select: {
 *             globalSelecthandler(e) {
 *                 let element = e.target;
 *                 let someElementTypeCheck = element.getAttribute('data-my-attribute');
 *
 *                 if (elementTypeCheck) {
 *                     // perform action
 *                 }
 *             }
 *         }
 *     },
 *     onLaunch(options) {
 *         // navigate to menu page
 *         ATV.Navigation.navigateToMenuPage();
 *         // or you can navigate to previously created page
 *         // ATV.Navigation.navigate('login');
 *     }
 * });
 *
 * @inner
 * @alias module:ATV.start
 * @fires onLaunch
 *
 * @param  {Object} cfg 		Configuration options
 */
function start(cfg = {}) {
	if (started) {
		console.warn('Application already started, cannot call start again.');
		return;
	}

    initLibraries(cfg);
	initAppHandlers(cfg);
    // if already bootloaded somewhere
    // immediately call the onLaunch method
    if (cfg.bootloaded) {
        App.onLaunch(App.launchOptions);
    }
	started = true;
}

/**
 * Reloads the application with the provided options and data.
 *
 * @example
 * ATV.reload({when: 'now'}, {customData});
 *
 * @inner
 * @alias module:ATV.reload
 * @fires onReload
 *
 * @param  {Object} [options]           Options value. {when: 'now'} // or 'onResume'
 * @param  {Object} [reloadData]        Custom data that needs to be passed while reloading the app
 */
function reload(options, reloadData) {
    App.onReload(options);
    App.reload(options, reloadData);
}

// add all utility methods
_.assign(libs, PubSub, {
    start: start,
    reload: reload
});

/**
 * Dependency free publish/subscribe for JavaScript.
 * @external pubsub-js
 * @see https://github.com/mroderick/PubSubJS
 */

/**
 * A minimalistic JavaScript SDK for Apple TV application development.
 * It assumes the code is run in an environment where [TVMLKit JS]{@link https://developer.apple.com/documentation/tvmljs} is present (or at least mocked).
 *
 * @module ATV
 * @extends external:pubsub-js
 *
 * @fires onLaunch
 * @fires onError
 * @fires onResume
 * @fires onSuspend
 * @fires onExit
 * @fires onReload
 *
 * @author eMAD <emad.alam@yahoo.com>
 */
module.exports = libs;