Simplified integration code to avoid to much duplication.

Added resizing script for new embed code.
Added iframe communicator when embedding.
d6
Frode Petterson 2015-02-09 10:22:14 +01:00
parent e12ba70b95
commit 18a3ed4a8f
3 changed files with 337 additions and 70 deletions

0
js/h5p-api.js Normal file
View File

155
js/h5p-resizer.js Normal file
View File

@ -0,0 +1,155 @@
// H5P iframe Resizer
(function () {
if (!window.postMessage || !window.addEventListener) {
return; // Not supported
}
// Map actions to handlers
var actionHandlers = {};
/**
* Prepare iframe resize.
*
* @private
* @param {Object} iframe Element
* @param {Object} data Payload
* @param {Function} respond Send a response to the iframe
*/
actionHandlers.hello = function (iframe, data, respond) {
// Make iframe responsive
iframe.style.width = '100%';
// Tell iframe that it needs to resize when our window resizes
var resize = function (event) {
if (iframe.contentWindow) {
// Limit resize calls to avoid flickering
respond('resize');
}
else {
// Frame is gone, unregister.
window.removeEventListener('resize', resize);
}
};
window.addEventListener('resize', resize, false);
// Respond to let the iframe know we can resize it
respond('hello');
};
/**
* Prepare iframe resize.
*
* @private
* @param {Object} iframe Element
* @param {Object} data Payload
* @param {Function} respond Send a response to the iframe
*/
actionHandlers.prepareResize = function (iframe, data, respond) {
responseData = {};
// Retain parent size to avoid jumping/scrolling
responseData.parentHeight = iframe.parentElement.style.height;
//iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
// Reset iframe height, in case content has shrinked.
iframe.style.height = '1px';
respond('resizePrepared', responseData);
};
/**
* Resize parent and iframe to desired height.
*
* @private
* @param {Object} iframe Element
* @param {Object} data Payload
* @param {Function} respond Send a response to the iframe
*/
actionHandlers.resize = function (iframe, data, respond) {
// Resize iframe so all content is visible.
iframe.style.height = data.height + 'px';
// Free parent
//iframe.parentElement.style.height = data.parentHeight;
};
/**
* Keyup event handler. Exits full screen on escape.
*
* @param {Event} event
*/
var escape = function (event) {
if (event.keyCode === 27) {
exitFullScreen();
}
};
/**
* Enter semi full screen.
* Expands the iframe so that it covers the whole page.
*
* @private
* @param {Object} iframe Element
* @param {Object} data Payload
* @param {Function} respond Send a response to the iframe
*/
actionHandlers.fullScreen = function (iframe, data, respond) {
iframe.style.position = 'fixed';
iframe.style.top = iframe.style.left = 0;
iframe.style.zIndex = 101;
iframe.style.width = iframe.style.height = '100%';
document.body.addEventListener('keyup', escape, false);
respond('fullScreen');
};
/**
* Exit semi full screen.
*
* @private
* @param {Object} iframe Element
* @param {Object} data Payload
* @param {Function} respond Send a response to the iframe
*/
actionHandlers.exitFullScreen = function (iframe, data, respond) {
iframe.style.position = '';
iframe.style.top = iframe.style.left = '';
iframe.style.zIndex = '';
iframe.style.width = '100%';
iframe.style.height = '';
document.body.removeEventListener('keyup', escape, false);
respond('exitFullScreen');
};
// Listen for messages from iframes
window.addEventListener('message', function receiveMessage(event) {
if (event.data.context !== 'h5p') {
return; // Only handle h5p requests.
}
// Find out who sent the message
var iframe, iframes = document.getElementsByTagName('iframe');
for (var i = 0; i < iframes.length; i++) {
if (iframes[i].contentWindow === event.source) {
iframe = iframes[i];
break;
}
}
if (!iframe) {
return; // Cannot find sender
}
// Find action handler handler
if (actionHandlers[event.data.action]) {
actionHandlers[event.data.action](iframe, event.data, function (action, data) {
if (data === undefined) {
data = {};
}
data.action = action;
data.context = 'h5p';
event.source.postMessage(data, event.origin);
});
}
}, false);
})();

252
js/h5p.js
View File

@ -31,6 +31,8 @@ else if (document.documentElement.msRequestFullscreen) {
// Keep track of when the H5Ps where started
H5P.opened = {};
H5P.canHasFullScreen = (H5P.isFramed && H5P.externalEmbed !== false) ? (document.fullscreenEnabled || document.webkitFullscreenEnabled || document.mozFullScreenEnabled || document.msFullscreenEnabled) : true;
/**
* Initialize H5P content.
* Scans for ".h5p-content" in the document and initializes H5P instances where found.
@ -39,15 +41,12 @@ H5P.init = function () {
// Useful jQuery object.
H5P.$body = H5P.jQuery(document.body);
// Prepare internal resizer for content.
var $window = H5P.jQuery(window.parent);
// H5Ps added in normal DIV.
var $containers = H5P.jQuery(".h5p-content").each(function () {
var $element = H5P.jQuery(this);
var $container = H5P.jQuery('<div class="h5p-container"></div>').appendTo($element);
var contentId = $element.data('content-id');
var contentData = H5PIntegration.getContentData(contentId);
var contentData = H5P.contentDatas['cid-' + contentId];
if (contentData === undefined) {
return H5P.error('No data for content id ' + contentId + '. Perhaps the library is gone?');
}
@ -85,7 +84,7 @@ H5P.init = function () {
H5P.openEmbedDialog($actions, contentData.embedCode);
});
}
if (H5PIntegration.showH5PIconInActionBar()) {
if (contentData.showH5PIconInActionBar) {
H5P.jQuery('<li><a class="h5p-link" href="http://h5p.org" target="_blank" title="' + H5P.t('h5pDescription') + '"></a></li>').appendTo($actions);
}
$actions.insertAfter($container);
@ -100,48 +99,98 @@ H5P.init = function () {
}
});
if (H5P.isFramed) {
// Make it possible to resize the iframe when the content changes size. This way we get no scrollbars.
var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId);
var resizeIframe = function () {
if (window.parent.H5P.isFullscreen) {
return; // Skip if full screen.
}
if (H5P.isFramed)
var resizeDelay;{
if (H5P.externalEmbed === false) {
// Internal embed
// Make it possible to resize the iframe when the content changes size. This way we get no scrollbars.
var iframe = window.parent.document.getElementById('h5p-iframe-' + contentId);
var resizeIframe = function () {
if (window.parent.H5P.isFullscreen) {
return; // Skip if full screen.
}
// Retain parent size to avoid jumping/scrolling
var parentHeight = iframe.parentElement.style.height;
iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
// Retain parent size to avoid jumping/scrolling
var parentHeight = iframe.parentElement.style.height;
iframe.parentElement.style.height = iframe.parentElement.clientHeight + 'px';
// Reset iframe height, in case content has shrinked.
iframe.style.height = '1px';
// Reset iframe height, in case content has shrinked.
iframe.style.height = '1px';
// Resize iframe so all content is visible.
iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px';
// Resize iframe so all content is visible.
iframe.style.height = (iframe.contentDocument.body.scrollHeight) + 'px';
// Free parent
iframe.parentElement.style.height = parentHeight;
};
// Free parent
iframe.parentElement.style.height = parentHeight;
};
var resizeDelay;
instance.$.on('resize', function () {
// Use a delay to make sure iframe is resized to the correct size.
clearTimeout(resizeDelay);
resizeDelay = setTimeout(function () {
resizeIframe();
}, 1);
});
instance.$.on('resize', function () {
// Use a delay to make sure iframe is resized to the correct size.
clearTimeout(resizeDelay);
resizeDelay = setTimeout(function () {
resizeIframe();
}, 1);
});
}
else if (H5P.communicator) {
// External embed
var parentIsFriendly = false;
// Handle hello message from our parent window
H5P.communicator.on('hello', function () {
// Initial setup/handshake is done
parentIsFriendly = true;
// Hide scrollbars for correct size
document.body.style.overflow = 'hidden';
H5P.communicator.send('prepareResize');
});
// When resize has been prepared tell parent window to resize
H5P.communicator.on('resizePrepared', function (data) {
H5P.communicator.send('resize', {
height: document.body.scrollHeight,
parentHeight: data.parentHeight
});
});
H5P.communicator.on('resize', function () {
instance.$.trigger('resize');
});
instance.$.on('resize', function () {
if (H5P.isFullscreen) {
return; // Skip iframe resize
}
// Use a delay to make sure iframe is resized to the correct size.
clearTimeout(resizeDelay);
resizeDelay = setTimeout(function () {
// Only resize if the iframe can be resized
if (parentIsFriendly) {
H5P.communicator.send('prepareResize');
}
else {
H5P.communicator.send('hello');
}
}, 0);
});
}
}
// Resize everything when window is resized.
$window.resize(function () {
if (window.parent.H5P.isFullscreen) {
// Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event.
if (!H5P.isFramed || H5P.externalEmbed === false) {
// Resize everything when window is resized.
H5P.jQuery(window.top).resize(function () {
if (window.parent.H5P.isFullscreen) {
// Use timeout to avoid bug in certain browsers when exiting fullscreen. Some browser will trigger resize before the fullscreenchange event.
instance.$.trigger('resize');
}
else {
instance.$.trigger('resize');
}
else {
instance.$.trigger('resize');
}
});
}
});
}
// Resize content.
instance.$.trigger('resize');
@ -151,11 +200,69 @@ H5P.init = function () {
H5P.jQuery("iframe.h5p-iframe").each(function () {
var contentId = H5P.jQuery(this).data('content-id');
this.contentDocument.open();
this.contentDocument.write('<!doctype html><html class="h5p-iframe"><head>' + H5PIntegration.getHeadTags(contentId) + '</head><body><div class="h5p-content" data-content-id="' + contentId + '"/></body></html>');
this.contentDocument.write('<!doctype html><html class="h5p-iframe"><head>' + H5P.getHeadTags(contentId) + '</head><body><div class="h5p-content" data-content-id="' + contentId + '"/></body></html>');
this.contentDocument.close();
this.contentWindow.H5P = {
externalEmbed: false
};
});
};
H5P.communicator = (function () {
/**
* @class
*/
function Communicator() {
var self = this;
// Maps actions to functions
var actionHandlers = {};
// Register message listener
window.addEventListener('message', function receiveMessage(event) {
if (window.parent !== event.source || event.data.context !== 'h5p') {
return; // Only handle messages from parent and in the correct context
}
if (actionHandlers[event.data.action] !== undefined) {
actionHandlers[event.data.action](event.data);
}
} , false);
/**
* Register action listener.
*
* @public
* @param {String} action What you are waiting for
* @param {Function} handler What you want done
*/
self.on = function (action, handler) {
actionHandlers[action] = handler;
};
/**
* Send a message to the all mighty father.
*
* @public
* @param {String} action
* @param {Object} [data] payload
*/
self.send = function (action, data) {
if (data === undefined) {
data = {};
}
data.context = 'h5p';
data.action = action;
// Parent origin can be anything
window.parent.postMessage(data, '*');
};
}
return (window.postMessage && window.addEventListener ? new Communicator() : undefined);
})();
/**
* Enable full screen for the given h5p.
*
@ -166,9 +273,19 @@ H5P.init = function () {
* @returns {undefined}
*/
H5P.fullScreen = function ($element, instance, exitCallback, body) {
if (H5P.isFramed) {
if (H5P.exitFullScreen !== undefined) {
return; // Cannot enter new fullscreen until previous is over
}
if (H5P.isFramed && H5P.externalEmbed === false) {
// Trigger resize on wrapper in parent window.
window.parent.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get());
window.top.H5P.fullScreen($element, instance, exitCallback, H5P.$body.get());
H5P.isFullscreen = true;
H5P.exitFullScreen = function () {
window.top.H5P.exitFullScreen();
H5P.isFullscreen = false;
H5P.exitFullScreen = undefined;
};
return;
}
@ -226,6 +343,7 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
instance.$.trigger('resize');
instance.$.trigger('focus');
H5P.exitFullScreen = undefined;
if (exitCallback !== undefined) {
exitCallback();
}
@ -235,6 +353,10 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
if (H5P.fullScreenBrowserPrefix === undefined) {
// Create semi fullscreen.
if (H5P.isFramed) {
return; // TODO: Ask parent for iframe
}
before('h5p-semi-fullscreen');
var $disable = H5P.jQuery('<div role="button" tabindex="1" class="h5p-disable-fullscreen" title="' + H5P.t('disableFullscreen') + '"></div>').appendTo($container.find('.h5p-content-controls'));
var keyup, disableSemiFullscreen = function () {
@ -277,6 +399,19 @@ H5P.fullScreen = function ($element, instance, exitCallback, body) {
var params = (H5P.fullScreenBrowserPrefix === 'webkit' && H5P.safariBrowser === 0 ? Element.ALLOW_KEYBOARD_INPUT : undefined);
$element[0][method](params);
}
// Allows everone to exit
H5P.exitFullScreen = function () {
if (H5P.fullScreenBrowserPrefix === '') {
document.exitFullscreen();
}
else if (H5P.fullScreenBrowserPrefix === 'moz') {
document.mozCancelFullScreen();
}
else {
document[H5P.fullScreenBrowserPrefix + 'ExitFullscreen']();
}
};
}
};
@ -300,7 +435,7 @@ H5P.getPath = function (path, contentId) {
}
if (contentId !== undefined) {
prefix = H5PIntegration.getContentPath(contentId);
prefix = H5P.url + '/content/' + contentId;
}
else if (window.H5PEditor !== undefined) {
prefix = H5PEditor.filesPath;
@ -316,18 +451,6 @@ H5P.getPath = function (path, contentId) {
return prefix + '/' + path;
};
/**
* THIS FUNCTION IS DEPRECATED, USE getPath INSTEAD
*
* Find the path to the content files folder based on the id of the content
*
* @param contentId
* Id of the content requesting a path
*/
H5P.getContentPath = function (contentId) {
return H5PIntegration.getContentPath(contentId);
};
/**
* Get library class constructor from H5P by classname.
* Note that this class will only work for resolve "H5P.NameWithoutDot".
@ -428,15 +551,15 @@ H5P.t = function (key, vars, ns) {
ns = 'H5P';
}
if (H5PIntegration.i18n[ns] === undefined) {
if (H5P.l10n[ns] === undefined) {
return '[Missing translation namespace "' + ns + '"]';
}
if (H5PIntegration.i18n[ns][key] === undefined) {
if (H5P.l10n[ns][key] === undefined) {
return '[Missing translation "' + key + '" in "' + ns + '"]';
}
var translation = H5PIntegration.i18n[ns][key];
var translation = H5P.l10n[ns][key];
if (vars !== undefined) {
// Replace placeholder with variables.
@ -946,7 +1069,7 @@ H5P.libraryFromString = function (library) {
* @returns {String} The full path to the library.
*/
H5P.getLibraryPath = function (library) {
return H5PIntegration.getLibraryPath(library);
return H5P.url + '/libraries/' + library;
};
/**
@ -1088,14 +1211,3 @@ if (String.prototype.trim === undefined) {
return H5P.trim(this);
};
}
// Finally, we want to run init when document is ready.
// TODO: Move to integration. Systems like Moodle using YUI cannot get its translations set before this starts!
if (H5P.jQuery) {
H5P.jQuery(document).ready(function () {
if (!H5P.initialized) {
H5P.initialized = true;
H5P.init();
}
});
}