File:Container.js
/**
* @module Container
* @namespace springroll
*/
(function(document, undefined)
{
//Import classes
var EventDispatcher = include('springroll.EventDispatcher'),
Features = include('springroll.Features'),
Bellhop = include('Bellhop'),
$ = include('jQuery');
/**
* The application container
* @class Container
* @extends springroll.EventDispatcher
* @constructor
* @param {string} iframeSelector jQuery selector for application iframe container
* @param {object} [options] Optional parameteres
* @param {string} [options.helpButton] jQuery selector for help button
* @param {string} [options.captionsButton] jQuery selector for captions button
* @param {string} [options.soundButton] jQuery selector for captions button
* @param {string} [options.voButton] jQuery selector for vo button
* @param {string} [options.sfxButton] jQuery selector for sounf effects button
* @param {string} [options.musicButton] jQuery selector for music button
* @param {string} [options.pauseButton] jQuery selector for pause button
* @param {string} [options.pauseFocusSelector='.pause-on-focus'] The class to pause
* the application when focused on. This is useful for form elements which
* require focus and play better with Application's keepFocus option.
*/
var Container = function(iframeSelector, options)
{
EventDispatcher.call(this);
/**
* The options
* @property {Object} options
* @readOnly
*/
this.options = options ||
{};
/**
* The name of this class
* @property {string} name
*/
this.name = 'springroll.Container';
/**
* The current iframe jquery object
* @property {jquery} iframe
*/
this.main = $(iframeSelector);
/**
* The DOM object for the iframe
* @property {Element} dom
*/
this.dom = this.main[0];
/**
* Communication layer between the container and application
* @property {Bellhop} client
*/
this.client = null;
/**
* The current release data
* @property {Object} release
*/
this.release = null;
/**
* Check to see if a application is loaded
* @property {Boolean} loaded
* @readOnly
*/
this.loaded = false;
/**
* Check to see if a application is loading
* @property {Boolean} loading
* @readOnly
*/
this.loading = false;
// Bind close failed handler
this._onCloseFailed = this._onCloseFailed.bind(this);
// Setup plugins
var plugins = Container._plugins;
for (var i = 0; i < plugins.length; i++)
{
plugins[i].setup.call(this);
}
};
/**
* The current version of the library
* @property {String} version
* @static
* @readOnly
* @default VERSION
*/
Container.version = VERSION;
//Reference to the prototype
var s = EventDispatcher.prototype;
var p = EventDispatcher.extend(Container);
/**
* The collection of Container plugins
* @property {Array} _plugins
* @static
* @private
*/
Container._plugins = [];
/**
* Open a application or path
* @method _internalOpen
* @protected
* @param {string} path The full path to the application to load
* @param {Object} [options] The open options
* @param {Boolean} [options.singlePlay=false] If we should play in single play mode
* @param {Object} [options.playOptions=null] The optional play options
*/
p._internalOpen = function(path, options)
{
options = $.extend(
{
singlePlay: false,
playOptions: null
}, options);
this.reset();
// Dispatch event for unsupported browsers
// and then bail, don't continue with loading the application
var err = Features.basic();
if (err)
{
/**
* Fired when the application is unsupported
* @event unsupported
* @param {String} err The error message
*/
return this.trigger('unsupported', err);
}
this.loading = true;
this.initClient();
// Open plugins
var plugins = Container._plugins;
for (var i = 0; i < plugins.length; i++)
{
plugins[i].open.call(this);
}
//Open the application in the iframe
this.main
.addClass('loading')
.prop('src', path);
// Respond with data when we're ready
this.client.respond('singlePlay', options.singlePlay);
this.client.respond('playOptions', options.playOptions);
/**
* Event when request to open an application has begin either by
* calling `openPath` or `openRemote`
* @event open
*/
this.trigger('open');
};
/**
* Open a application or path
* @method openPath
* @param {string} path The full path to the application to load
* @param {Object} [options] The open options
* @param {Boolean} [options.singlePlay=false] If we should play in single play mode
* @param {Object} [options.playOptions=null] The optional play options
*/
p.openPath = function(path, options, playOptions)
{
options = options ||
{};
// This should be deprecated, support for old function signature
if (typeof options === "boolean")
{
options = {
singlePlay: singlePlay,
playOptions: playOptions
};
}
this._internalOpen(path, options);
};
/**
* Set up communication layer between site and application.
* May be called from subclasses if they create/destroy Bellhop instances.
* @protected
* @method initClient
*/
p.initClient = function()
{
//Setup communication layer between site and application
this.client = new Bellhop();
this.client.connect(this.dom);
//Handle bellhop events coming from the application
this.client.on(
{
loading: onLoading.bind(this),
loadDone: onLoadDone.bind(this), // @deprecated use 'loaded' instead
loaded: onLoadDone.bind(this),
endGame: onEndGame.bind(this),
localError: onLocalError.bind(this)
});
};
/**
* Removes the Bellhop communication layer altogether.
* @protected
* @method destroyClient
*/
p.destroyClient = function()
{
if (this.client)
{
this.client.destroy();
this.client = null;
}
};
/**
* Handle the local errors
* @method onLocalError
* @private
* @param {Event} event Bellhop event
*/
var onLocalError = function(event)
{
this.trigger(event.type, event.data);
};
/**
* The game is starting to load
* @method onLoading
* @private
*/
var onLoading = function()
{
/**
* Event when a application start loading, first even received
* from the Application.
* @event opening
*/
this.trigger('opening');
};
/**
* Reset the mutes for audio and captions
* @method onLoadDone
* @private
*/
var onLoadDone = function()
{
this.loading = false;
this.loaded = true;
this.main.removeClass('loading');
var plugins = Container._plugins;
for (var i = 0; i < plugins.length; i++)
{
plugins[i].opened.call(this);
}
/**
* Event when the application gives the load done signal
* @event opened
*/
this.trigger('opened');
};
/**
* The application ended and destroyed itself
* @method onEndGame
* @private
*/
var onEndGame = function()
{
this.reset();
};
/**
* Reset all the buttons back to their original setting
* and clear the iframe.
* @method reset
*/
p.reset = function()
{
var wasLoaded = this.loaded || this.loading;
// Destroy in the reverse priority order
if (wasLoaded)
{
var plugins = Container._plugins;
for (var i = plugins.length - 1; i >= 0; i--)
{
plugins[i].closed.call(this);
}
}
// Remove bellhop instance
this.destroyClient();
// Reset state
this.loaded = false;
this.loading = false;
// Clear the iframe src location
this.main.attr('src', '')
.removeClass('loading');
if (wasLoaded)
{
this.off('localError', this._onCloseFailed);
/**
* Event when a application closes
* @event closed
*/
this.trigger('closed');
}
};
/**
* Tell the application to start closing
* @method close
*/
p.close = function()
{
if (this.loading || this.loaded)
{
var plugins = Container._plugins;
for (var i = plugins.length - 1; i >= 0; i--)
{
plugins[i].close.call(this);
}
/**
* Event when a application starts closing
* @event close
*/
this.trigger('close');
/**
* There was an uncaught iframe error destroying the game on closing
* @event localError
* @param {Error} error The error triggered
*/
this.once('localError', this._onCloseFailed);
// Start the close
this.client.send('close');
}
else
{
this.reset();
}
};
/**
* If there was an error when closing, reset the container
* @method _onCloseFailed
* @private
*/
p._onCloseFailed = function()
{
this.reset(); // force close the app
};
/**
* Destroy and don't use after this
* @method destroy
*/
p.destroy = function()
{
this.reset();
s.destroy.call(this);
// Destroy in the reverse priority order
var plugins = Container._plugins;
for (var i = plugins.length - 1; i >= 0; i--)
{
plugins[i].teardown.call(this);
}
this.main = null;
this.options = null;
this.dom = null;
};
namespace('springroll').Container = Container;
}(document));