menu.js

import _ from 'lodash';
import Parser from './parser';

// base menu string for initial document creation
const docStr = '<document><menuBarTemplate><menuBar></menuBar></menuBarTemplate></document>';

// indicate whether the menu was created
let created = false;

// few private instances
let doc = Parser.dom(docStr);
let menuBarEl = (doc.getElementsByTagName('menuBar')).item(0);
let menuBarFeature = menuBarEl && menuBarEl.getFeature('MenuBarDocument');
let itemsCache = {};

// default menu options
let defaults = {
    attributes: {},
    items: []
};

/**
 * Sets the default menu options
 *
 * @inner
 * @alias module:menu.setOptions
 *
 * @param {Object} cfg The configuration object
 */
function setOptions(cfg = {}) {
    console.log('setting menu options...', cfg);
    // override the default options
    _.assign(defaults, cfg);
}

/**
 * Iterates and sets attributes to an element.
 *
 * @private
 * 
 * @param {Element} el 			The element to set attributes on
 * @param {Object} attributes 	Attributes key value pairs.
 */
function setAttributes(el, attributes) {
	console.log('setting attributes on element...', el, attributes);
    _.each(attributes, (value, name) => el.setAttribute(name, value));
}

/**
 * Returns instance of the menu document (auto create if not already created)
 *
 * @inner
 * @alias module:menu.get
 * 
 * @return {Document}		Instance of the created menu document.
 */
function get() {
    if (!created) {
        create();
    }
    return doc;
}

/**
 * Adds menu item to the menu document.
 *
 * @private
 * 
 * @param {Object} item 	The configuration realted to the menu item.
 */
function addItem(item = {}) {
    if (!item.id) {
        console.warn('Cannot add menuitem. A unique identifier is required for the menuitem to work correctly.');
        return;
    }
    let el = doc.createElement('menuItem');
    // assign unique id
    item.attributes = _.assign({}, item.attributes, {
        id: item.id
    });
    // add all attributes
    setAttributes(el, item.attributes);
    // add title
    el.innerHTML = `<title>${(_.isFunction(item.name) ? item.name() : item.name)}</title>`;
    // add page reference
    el.page = item.page;
    // appends to the menu
    menuBarEl.insertBefore(el, null);
    // cache for later use
    itemsCache[item.id] = el;

    return el;
}

/**
 * Generates a menu from the configuration obejct.
 *
 * @example
 * ATV.Menu.create({
 *     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
 *     }]
 * });
 *
 * @inner
 * @alias module:menu.create
 * 
 * @param  {Object} cfg 		Menu related configurations
 * @return {Document}     		The created menu document
 */
function create(cfg = {}) {
    if (created) {
        console.warn('An instance of menu already exists, skipping creation...');
        return;
    }
    // defaults
    _.assign(defaults, cfg);
    
    console.log('creating menu...', defaults);
    
    // set attributes to the menubar element
    setAttributes(menuBarEl, defaults.attributes);
    // add all items to the menubar
    _.each(defaults.items, (item) => addItem(item));
    // indicate done
    created = true;

    return doc;
}

/**
 * Associate a document to the menuitem (using the menuitem's unique id).
 *
 * @inner
 * @alias module:menu.setDocument
 * 
 * @param {Document} doc        	The document to associate with the menuitem
 * @param {String} menuItemid		The id of the menu item as per the configuration
 */
function setDocument(doc, menuItemid) {
    let menuItem = itemsCache[menuItemid];

    if (!menuItem) {
        console.warn(`Cannot set document to the menuitem. The given id ${menuItemid} does not exist.`);
        return;
    }
    menuBarFeature.setDocument(doc, menuItem);
}

/**
 * Set the given menuitem as active (using the menuitem's unique id).
 *
 * @inner
 * @alias module:menu.setSelectedItem
 * 
 * @param {String} menuItemid 		The id of the menu item as per the configuration
 */
function setSelectedItem(menuItemid) {
    let menuItem = itemsCache[menuItemid];

    if (!menuItem) {
        console.warn(`Cannot select menuitem. The given id ${menuItemid} does not exist.`);
        return;
    }
    menuBarFeature.setSelectedItem(menuItem);
}

/**
 * A very minimalistic library to manage Apple TV menu bars.
 *
 * @module menu
 *
 * @author eMAD <emad.alam@yahoo.com>
 *
 */
export default {
    /**
     * Whether the menu was already created.
     * @return {Boolean} Created
     */
    get created() { return created; },
    set created(val) { },
    setOptions: setOptions,
    create: create,
    get: get,
    setDocument: setDocument,
    setSelectedItem: setSelectedItem,
    /**
     * Get the menu loading message if provided in the config
     * @return {String} Loading message
     */
    getLoadingMessage() {
    	return (_.isFunction(defaults.loadingMessage) ? defaults.loadingMessage() : defaults.loadingMessage);
    },
    /**
     * Get the menu error message if provided in the config
     * @return {String} Error message
     */
    getErrorMessage(){
        return (_.isFunction(defaults.errorMessage) ? defaults.errorMessage() : defaults.errorMessage);
    }
};