The Instructure Community will enter a read-only state on November 22, 2025 as we prepare to migrate to our new Community platform in early December.
Read our blog post for more info about this change.
Found this content helpful? Log in or sign up to leave a like!
Has anyone had success integrating the Amara embed code in their global JavaScript override file in the new UI?
Outside of Canvas you can add their <script> tag as such:
<script type="text/javascript" src='https://amara.org/embedder-iframe'>
</script>
Since there's no self-report for successful inclusion of the script in their code, I'm not sure if it's happening successfully if I use $.getScript or adding a new <script> tag to the DOM by using .appendChild. Any tips would be appreciated.
Solved! Go to Solution.
Updated (5/31/2016): I revised below the jQuery code segment to explain the use of JavaScript within the jQuery instead of pure jQuery.
Linked JavaScript and CSS added via Node.appendChild() is executed immediately.
Here's a simply pure-JavaScript function for dynamically loading JavaScript and CSS files:
function loadFile(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.type = 'text/javascript';
link.src = fileURL;
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = fileURL;
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
window.onload = (function() {
loadFile('https://amara.org/embedder-iframe', 'js');
})();
Note: The URL for the Amara Embedder script file should be ran across HTTPS for security. As of my posting this, it works directly, but is Amara ever disables secure access, a third-party system will need to be used that will convert your secure request into an unsecure one (middleman).
Here's a jQuery extension equivalent:
$.fn.extend({
loadFile: function(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.type = 'text/javascript';
link.src = fileURL;
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = fileURL;
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
});
$(function() {
$().loadFile('https://amara.org/embedder-iframe', 'js');
});
Note: Pure JavaScript is used for the Node creation and appending because jQuery will not load and execute the additional JavaScript as expected. I've tested the issue to a fairly decent extent, dropping my crude extension and using only $.getScript(), $.append(), and $.appendTo() with identical results. Regardless of how the SCRIPT element is generated, using jQuery to add it to the page (HEAD or BODY) will result it the script be executed as if it were on the server calling it and never. The Amara script relies upon the ability to pull the information without this happening as it is extracting its own domain information from the link.
Here're the lines in the Amara script that make a pure jQuery solution impossible:
var scriptFiles = document.getElementsByTagName("script");
var THIS_JS_FILE = scriptFiles[scriptFiles.length-1].src;
// ...
var parser = document.createElement('a');
parser.href = THIS_JS_FILE;
// ...
var iframe = document.createElement("IFRAME");
iframe.src = parser.protocol + "//" + parser.host + "/embedder-widget-iframe/";
These lines are spread out a bit through the actual file, but can be tested without the rest for anyone that wishes to confirm my findings.
Updated (5/31/2016): I revised below the jQuery code segment to explain the use of JavaScript within the jQuery instead of pure jQuery.
Linked JavaScript and CSS added via Node.appendChild() is executed immediately.
Here's a simply pure-JavaScript function for dynamically loading JavaScript and CSS files:
function loadFile(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.type = 'text/javascript';
link.src = fileURL;
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = fileURL;
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
window.onload = (function() {
loadFile('https://amara.org/embedder-iframe', 'js');
})();
Note: The URL for the Amara Embedder script file should be ran across HTTPS for security. As of my posting this, it works directly, but is Amara ever disables secure access, a third-party system will need to be used that will convert your secure request into an unsecure one (middleman).
Here's a jQuery extension equivalent:
$.fn.extend({
loadFile: function(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.type = 'text/javascript';
link.src = fileURL;
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = fileURL;
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
});
$(function() {
$().loadFile('https://amara.org/embedder-iframe', 'js');
});
Note: Pure JavaScript is used for the Node creation and appending because jQuery will not load and execute the additional JavaScript as expected. I've tested the issue to a fairly decent extent, dropping my crude extension and using only $.getScript(), $.append(), and $.appendTo() with identical results. Regardless of how the SCRIPT element is generated, using jQuery to add it to the page (HEAD or BODY) will result it the script be executed as if it were on the server calling it and never. The Amara script relies upon the ability to pull the information without this happening as it is extracting its own domain information from the link.
Here're the lines in the Amara script that make a pure jQuery solution impossible:
var scriptFiles = document.getElementsByTagName("script");
var THIS_JS_FILE = scriptFiles[scriptFiles.length-1].src;
// ...
var parser = document.createElement('a');
parser.href = THIS_JS_FILE;
// ...
var iframe = document.createElement("IFRAME");
iframe.src = parser.protocol + "//" + parser.host + "/embedder-widget-iframe/";
These lines are spread out a bit through the actual file, but can be tested without the rest for anyone that wishes to confirm my findings.
Thank you cesbrandt
Another thing I was hoping to see by posting my question is to see if anyone else in the Community actually was successful in getting it to work with a real embed code in a page of editable content such as a wiki/content page in a course. It would seem that when the page is saved that it strips out any of the data* attributes that the <div> tag would need to supply information to the Amara embed script.
So after using your code suggestion for a jQuery method, I have no way of knowing if the <script> node that it adds is actually in the document since there's nothing for it to reference if I use the embed code in the page as suggested by the Amara website:
<div class="amara-embed" data-height="480px" data-width="854px" data-url="...">
</div>
So I'll have to test this with a self executing script on another https server that executes a console.log() or something similar. In the meantime I'm really appreciative of your willingness to share your strategy and research.
Just informing here that this is the correct code for loading external .js files into Canvas outside of the built-in Theme Editor JS override file upload capability.
Thank you Christopher!
Edit: Edited the jQuery because I somehow didn't have the data-url attribute assignment included. Not sure how I missed it (I copied the snippet from my testbed), but my apologies for any trouble that caused. ^^'
Alright, so, I've come up with a bit of a "cheat" to make it work. It will require modifying the JavaScript AND the HTML added to the RCE.
Here's the modified jQuery solution:
$.fn.extend({
loadFile: function(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.type = 'text/javascript';
link.src = fileURL;
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = fileURL;
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
});
$(function() {
$.each($('.amara-embed'), function(i, val) {
$(val).attr({'data-height': $(val).css('height'), 'data-width': $(val).css('width')});
var dataVal = (typeof $(val).attr('role') !== 'undefined' && $(val).attr('role') !== false) ? $(val).attr('role').split('**|**') : '';
if(dataVal.length >= 1) {
$(val).attr('data-url', dataVal[0]);
}
if(dataVal.length >= 2 && dataVal[1] != '') {
$(val).attr('data-hide-order', dataVal[1]);
}
if(dataVal.length >= 3 && dataVal[2] != '') {
$(val).attr('data-initial-language', dataVal[2]);
}
if(dataVal.length >= 4 && dataVal[3] != '') {
$(val).attr('data-show-subtitles-default', dataVal[3]);
}
if(dataVal.length >= 5 && dataVal[4] != '') {
$(val).attr('data-show-transcript-default', dataVal[4]);
}
$(val).removeAttr('style').removeAttr('role');
});
$().loadFile('https://amara.org/embedder-iframe', 'js');
});
The pure JavaScript equivalent:
function loadFile(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.setAttribute('type', 'text/javascript');
link.setAttribute('src', fileURL);
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', fileURL);
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
window.onload = (function() {
var amara = document.getElementsByClassName('amara-embed');
for(var i = 0; i < amara.length; i++) {
amara[i].dataset.height = amara[i].style.height;
amara[i].dataset.width = amara[i].style.width;
var dataVal = (amara[i].hasAttribute('role')) ? amara[i].getAttribute('role').split('**|**') : '';
if(dataVal.length >= 1) {
amara[i].dataset.url = dataVal[0];
}
if(dataVal.length >= 2 && dataVal[1] != '') {
amara[i].dataset.hideOrder = dataVal[1];
}
if(dataVal.length >= 3 && dataVal[2] != '') {
amara[i].dataset.initialLanguage = dataVal[2];
}
if(dataVal.length >= 4 && dataVal[3] != '') {
amara[i].dataset.showSubtitlesDefault = dataVal[3];
}
if(dataVal.length >= 5 && dataVal[4] != '') {
amara[i].dataset.showTranscriptDefault = dataVal[4];
}
amara[i].removeAttribute('style');
amara[i].removeAttribute('role');
};
loadFile('https://amara.org/embedder-iframe', 'js');
})();
Now, here's the bread-and-butter, the modified HTML code:
<div class="amara-embed" style="height: 480px; width: 854px;" role="http://www.youtube.com/watch?v=5CKwCfLUwj4"></div>
Note: The role attribute replaces ALL the dataset attributes EXCEPT height and width in the following order:
These values should be separated by "**|**" (not including the double quotation marks). A separator is needed for each option before the last option used. So, if you have the URL and want subtitles on by default, the role attribute should be:
role="http://www.youtube.com/watch?v=5CKwCfLUwj4**|****|****|**true"
The script won't create a dataset attribute for a blank value, so you don't need to be concerned with making sure there is a value for each one, but the separators are absolutely required to match the values to the appropriate dataset attribute.
This is, in my honest opinion, an ugly hack to make it work, but it's likely the easiest solution with the widest compatibility you could implement.
Interesting approach, Christopher
After fiddling with either version I wanted to point out my observations from a Chrome Dev Tools debug session using your HTML code modification in a single Canvas Page:
<div class="amara-embed" style="height: 480px; width: 854px;" role="http://www.youtube.com/watch?v=5CKwCfLUwj4"></div>
I can query the <div> using Chrome dev tools after the page loads, but the script fails to do so. Using the pure JavaScript solution, I place a break point at this line:
var amara = document.getElementsByClassName('amara-embed');
But the array returned by the document.getElementsByClassName by your code returns an empty array [] when using the Chrome step debug tool. So I'm wondering why your script can't find it. Should it be called on delay via setTimeout?
Take a look at these:
JavaScript: https://jsfiddle.net/quxvyde7/1/embedded/
jQuery: https://jsfiddle.net/p54tdesL/4/embedded/
Are either of them working for you?
I only ask because I have confirmed both from multiple computers and browsers.
Yes I've tried both ways. Here's a screencast of me using the pure JavaScript version and the workflow I've been encountering:
Amara Embed Troubleshooting - YouTube
Maybe you could show me what I'm doing wrong.
Thanks!
I just tried my course in an incognito window in Chrome and it works. So I think it's a browser extension that is blocking it. Now to figure out which one...
I see where my mistake was. You are correct, it's the most common issue with custom JavaScript on Canvas: timing. I missed it because I tested on a local Canvas server, instead of an Instructure instance. ^^'
I've restructured it to use setInterval() to ensure it has ample opportunity to execute.
JavaScript:
function loadFile(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.setAttribute('type', 'text/javascript');
link.setAttribute('src', fileURL);
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', fileURL);
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
function embedAmara(amara) {
for(var i = 0; i < amara.length; i++) {
amara[i].dataset.height = amara[i].style.height;
amara[i].dataset.width = amara[i].style.width;
var dataVal = (amara[i].hasAttribute('role')) ? amara[i].getAttribute('role').split('**|**') : '';
if(dataVal.length >= 1) {
amara[i].dataset.url = dataVal[0];
}
if(dataVal.length >= 2 && dataVal[1] != '') {
amara[i].dataset.hideOrder = dataVal[1];
}
if(dataVal.length >= 3 && dataVal[2] != '') {
amara[i].dataset.initialLanguage = dataVal[2];
}
if(dataVal.length >= 4 && dataVal[3] != '') {
amara[i].dataset.showSubtitlesDefault = dataVal[3];
}
if(dataVal.length >= 5 && dataVal[4] != '') {
amara[i].dataset.showTranscriptDefault = dataVal[4];
}
amara[i].removeAttribute('style');
amara[i].removeAttribute('role');
};
loadFile('https://amara.org/embedder-iframe', 'js');
}
window.onload = (function() {
var i = 0, timeout = 30, interval = 500;
var wait = setInterval(function() {
var amara = document.getElementsByClassName('amara-embed');
if(amara.length != 0) {
embedAmara(amara);
clearInterval(wait);
} else if(i == timeout) {
clearInterval(wait);
}
i++;
}, interval);
})();
jQuery:
$.fn.extend({
loadFile: function(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.type = 'text/javascript';
link.src = fileURL;
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = fileURL;
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
});
function embedAmara() {
$.each($('.amara-embed'), function(i, val) {
$(val).attr({'data-height': $(val).css('height'), 'data-width': $(val).css('width')});
var dataVal = (typeof $(val).attr('role') !== 'undefined' && $(val).attr('role') !== false) ? $(val).attr('role').split('**|**') : '';
if(dataVal.length >= 1) {
$(val).attr('data-url', dataVal[0]);
}
if(dataVal.length >= 2 && dataVal[1] != '') {
$(val).attr('data-hide-order', dataVal[1]);
}
if(dataVal.length >= 3 && dataVal[2] != '') {
$(val).attr('data-initial-language', dataVal[2]);
}
if(dataVal.length >= 4 && dataVal[3] != '') {
$(val).attr('data-show-subtitles-default', dataVal[3]);
}
if(dataVal.length >= 5 && dataVal[4] != '') {
$(val).attr('data-show-transcript-default', dataVal[4]);
}
$(val).removeAttr('style').removeAttr('role');
});
$().loadFile('https://amara.org/embedder-iframe', 'js');
}
$(function() {
var i = 0, timeout = 30, interval = 500;
var wait = setInterval(function() {
if($('.amara-embed').length != 0) {
embedAmara();
clearInterval(wait);
} else if(i == timeout) {
clearInterval(wait);
}
i++;
}, interval);
});
This is working on our Beta instance. If it doesn't work, I'm afraid I'm out of ideas. :S
I really appreciate all the work you've put into this, Christopher, but it still isn't reliably working for me on all browsers. Since it's more or less a hack/workaround, I think we'll elect to just use HTML files in the files repository for any Amara video embeds until hopefully one day this feature request gets enough attention to warrant an update to the RCE:
My apologies, JEFHQ12951, but last night I didn't have a lot of options for diverse testing. Testing the setInterval() versions, now, I am also seeing an inconsistency in its loading success.
The issue isn't even with the dataset attributes being blacklisted. Though that is troublesome, it's an issue that has been solved 100%. At this point, the problem is with Canvas' dynamic content loading. Sadly, there really isn't any better way to deal with it than setInterval(), as far as I'm aware. Though I try to help with JavaScript solutions, I'm no expert at the language and do it mostly to gain experience with the language. ^^'
Now, regarding the latest of identified issues with making Amara work, I've traced it down to the recognition of the load event. Inside of the setInterval(), the event isn't recognized while it is recognized from without. This appears to be caused by the script file being loaded while the main script still has additional code to execute.
After bouncing some ideas around with some friends that are MUCH better at JavaScript than I, we came up with a few ideas that I've trying out all day. The most promising one, in theory, was dynamically generating IFRAMEs to replace the DIVs with an HTML page built perfectly to run Amara, but no matter how I went about this, only using a third-party host could I get it to work. The concept being that you supply the video dataset to the third-party host as part of the URL of the IFRAME and it renders the page using server-side languages so that the content is fully built before it ever reaches the browser. Any other attempt produced errors where Amara wouldn't recognize the targetOrigin.
Several more ideas failed for the same reasons, the Amara script wasn't designed to allow such abstract usage.
I did find a "solution" that has worked for me, however, it is also incorrect (you'll get an error). The idea is to trick the browser into re-executing the window.onload function AFTER the Amara JavaScript file has been linked and it has been consistently successful while testing in Google Chrome 50, Mozilla Firefox 46, and Internet Explorer 11 on Windows 7 and Safari 9 on OS X.
I'm not particularly familiar with the Amara embedder, but general appearance/functionality appears to cooperate with this modification.
I had to, once again, expand the snippets to accommodate functionality (such as ensuring the dataset isn't broken by the window.onload function being ran twice). Included in the additional functionality is a prototype I wrote for retrieving a list of DOM elements by TagName, AttributeName, and AttributeValue. It's only used to keep from linking the Amara JavaScript file twice, but I didn't see any reason to write the detection into the script when I already had the prototype.
There's only a JavaScript version of this one. I haven't had the time to write a jQuery equivalent (had a niece born a little earlier today), but if you can confirm that this version works for you, I'll make a jQuery equivalent a priority for tomorrow.
JavaScript
Node.prototype.getElementsByTagNameAndAttribute = function(tag, attribute, value) {
var dom = this.all || this.getElementsByTagName(tag);
var match = new Array();
for(var i = 0; i < dom.length; i++) {
if((typeof dom[i]) === 'object') {
if((attribute !== undefined && (value !== undefined && dom[i].getAttribute(attribute) === value) || (value === undefined && dom[i].hasAttribute(attribute))) || (attribute === undefined && value === undefined)) {
match.push(dom[i]);
}
}
}
return match;
};
function loadFile(fileURL, type) {
switch(type) {
case 'js':
var link = document.createElement('script');
link.setAttribute('type', 'text/javascript');
link.setAttribute('src', fileURL);
document.getElementsByTagName('body')[0].appendChild(link);
break;
case 'css':
var link = document.createElement('style');
link.setAttribute('rel', 'stylesheet');
link.setAttribute('type', 'text/css');
link.setAttribute('href', fileURL);
document.getElementsByTagName('head')[0].appendChild(link);
break;
}
}
function embedAmara(amara) {
for(var i = 0; i < amara.length; i++) {
if(amara[i].hasAttribute('style')) {
amara[i].dataset.height = amara[i].style.height;
amara[i].dataset.width = amara[i].style.width;
amara[i].removeAttribute('style');
}
if(amara[i].hasAttribute('role')) {
var dataVal = (amara[i].hasAttribute('role')) ? amara[i].getAttribute('role').split('**|**') : '';
if(dataVal.length >= 1) {
amara[i].dataset.url = dataVal[0];
}
if(dataVal.length >= 2 && dataVal[1] != '') {
amara[i].dataset.hideOrder = dataVal[1];
}
if(dataVal.length >= 3 && dataVal[2] != '') {
amara[i].dataset.initialLanguage = dataVal[2];
}
if(dataVal.length >= 4 && dataVal[3] != '') {
amara[i].dataset.showSubtitlesDefault = dataVal[3];
}
if(dataVal.length >= 5 && dataVal[4] != '') {
amara[i].dataset.showTranscriptDefault = dataVal[4];
}
amara[i].removeAttribute('role');
}
}
var amaraURL = 'https://amara.org/embedder-iframe';
if(document.getElementsByTagNameAndAttribute('script', 'src', amaraURL).length == 0) {
loadFile(amaraURL, 'js');
}
}
window.onload = (function() {
var i = 0, timeout = 30, interval = 500;
var wait = setInterval(function() {
var amara = document.getElementsByClassName('amara-embed');
if(amara.length != 0) {
embedAmara(amara);
onload();
clearInterval(wait);
} else if(i == timeout) {
clearInterval(wait);
}
i++;
}, interval);
})();
Community helpTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign inTo interact with Panda Bot, our automated chatbot, you need to sign up or log in:
Sign in