Cannot communicate between captivate html and parent html - javascript

I have a published captivate html file that is loaded into an iframe of another html. I cannot communicate between the two, not even with localStorage. Can anyone tell me what I'm missing?
Parent html
var everythingLoaded = setInterval(function () {
if (/loaded|complete/.test(document.readyState)) {
clearInterval(everythingLoaded);
init();
}
}, 10);
function init() {
ScormProcessInitialize();
var studentID = ScormProcessGetValue("cmi.core.student_id");
var student_name = ScormProcessGetValue ("cmi.core.student_name");
var nameArraya = student_name.split(" ");
var nameArrayb = nameArraya[1].split(",");
var studentNumber = nameArrayb[0];
ScormProcessSetValue("cmi.core.lesson_status", "incomplete");
localStorage.setItem("_studentNumber", studentNumber);
alert("Student Number: " + studentNumber + " Student Mame: " + student_name);
setTimeout(function () {
document.getElementById("iFrame_a").innerHTML = "<iframe name='iframe_1' id='frame_1' src='//somepath.com/sandbox/somecourse/index.html' frameborder='0' width='1000px' height='605px'></iframe>";
}, 250);
}
function sendComplete() {
alert("Send from index start!");
ScormProcessSetValue("cmi.core.lesson_status", "completed");
alert("send status: Completed");
}
window.onbeforeunload = function (){
cpInfoCurrentSlide = localStorage.getItem("_cpInfoCurrentSlide")
alert(cpInfoCurrentSlide);
if(cpInfoCurrentSlide >= 40)
{
alert("onbeforeunload called: " + cpInfoCurrentSlide )
ScormProcessSetValue("cmi.core.lesson_status", "completed");
}
}
iframe code snippet
localStorage.setItem("_cpInfoCurrentSlide", cpInfoCurrentSlide);

I believe your problem is with onbeforeunload. As I remember captivate packages clobber any functions associated with onbeforeunload in the parent frame when they load.
Try this instead, override your SCORM api setvalue method:
var oldLMSSetValue = window.API.LMSSetValue;
window.API.LMSSetValue = function(key, value){
if(key === 'cmi.core.lesson_status' && value === 'completed'){
//do your stuff here
cpInfoCurrentSlide = localStorage.getItem("_cpInfoCurrentSlide")
alert(cpInfoCurrentSlide);
}
//call the original scorm api function so that it runs as expected.
oldLMSSetValue(key,value);
};
edit: this code would go in the parent window, not the iframe.

Related

Office.js outlook add-in issue

I'm trying to get the Body in Outlook and then update/set it with categories. My issue is this - when I debug it - it works fine. But when I don't debug from function to function - it gets all the way to the last function and just stops - updateBody(). What's really strang is if I remove the breakpoints on each function and just set a breakpoint on last function - never gets hit, but console will write out "Starting update body". All the console.logs are writing out data as expected. Not sure what is going on. Appreciate any help! Thanks.
"use strict";
var item;
var response;
var tags;
var updatedBody;
Office.initialize = function () {
$(document).ready(function () {
// The document is ready
item = Office.context.mailbox.item;
debugger;
getBodyType();
});
}
function getBodyType() {
item.body.getTypeAsync(
function (resultBody) {
if (resultBody.status == Office.AsyncResultStatus.Failed) {
write(resultBody.error.message);
} else {
response = resultBody;
console.log('Successfully got BodyType');
console.log(response.value);
getCategories();
}
});
}
function getCategories() {
tags = "";
// Successfully got the type of item body.
// Set data of the appropriate type in body.
item.categories.getAsync(function (asyncResult) {
if (asyncResult.status === Office.AsyncResultStatus.Failed) {
console.log("Action failed with error: " + asyncResult.error.message);
} else {
var categories = asyncResult.value;
console.log("Categories:");
categories.forEach(function (item) {
var tag = item.displayName;
tags += '#' + tag.replace(/\s/g, "") + ' ';
});
console.log('Successfully got tags');
console.log(tags);
getBody();
}
});
}
function getBody() {
var body = "";
updatedBody = "";
console.log("Starting get body");
if (response.value == Office.MailboxEnums.BodyType.Html) {
item.body.getAsync(
Office.CoercionType.Html,
{ asyncContext: "This is passed to the callback" },
function (result) {
//Replace all the # tags and update again.
body = result.value.replaceAll(/#(\w)+/g, "").trimEnd();
var domParser = new DOMParser();
var parsedHtml = domParser.parseFromString(body, "text/html");
$("body", parsedHtml).append("<div>" + tags + "</div>");
var changedString = (new XMLSerializer()).serializeToString(parsedHtml);
if (changedString != "") {
updatedBody = changedString;
}
console.log(updatedBody);
updateBody();
});
}
}
function updateBody() {
console.log("Starting update body");
item.body.setAsync(
updatedBody,
{ coercionType: Office.CoercionType.Html },
function (result2) {
console.log("Body updated");
});
}
Image - With breakpoints on each function - works as expected
Image - Without breakpoints - gets to updateBody() function.
But the string updatedBody isn't logged. It somehow skips over that
even though it's called before updateBody() on getBody()
Image - Same code run via Script Lab - works just fine as well.

how to approch this object loading problem with javascript and HTML

I am working on a web application, I am just using javascript at the moment. The problem that I am trying to solve is that I have three different objects and only one HTML page. Based on the user click event, I want the objects for the chosen category to be loaded and displayed on the same page. For example, let's say the user is at the home page, if they click on category A from the navigation bar, the page will be loaded first and then the script will load the objects to the data structure. Finally, display them to the javascript generated HTML containers. The same thing should happen with a different category after the User click Event is fired. To be more precise I want to be able to reuse the HTML page for different objects without having to create a page for each category.
I already have created the code that does all of the data loading and HTML generation for the n objects I want to load. The code works fine when I am at the object's page but if the event is fired from another page nothing seems to happen. I am guessing this has to do with page loading timing.
I have posted the complete code of the part that I am working on.
var dataController = (function() {
var JSONurls = {
bags: "../JSON/bags.json",
bc: "../JSON/briefcases.json",
belts: "../JSON/belts.json",
accs: "../JSON/accs.json",
};
ProductObj = function(name, des, colors, price, pics, type, ID) {
this.name = name;
this.description = des;
this.colors = colors;
this.price = price;
this.pics = pics;
this.type = type;
this.ID = ID;
};
var dataStruc = {
allProducts: {
bags: [],
briefcases: [],
belts: [],
accessories: [],
},
};
return {
addProd: function(obj) {
var newProd, ID;
if (dataStruc.allProducts[obj.type].length > 0) {
ID =
dataStruc.allProducts[obj.type][
dataStruc.allProducts[obj.type].length - 1
].ID + 1;
} else {
ID = 0;
}
newProd = new ProductObj(
obj.name,
obj.description,
obj.colors,
obj.price,
obj.pics,
obj.type,
obj.ID
);
dataStruc.allProducts[obj.type].push(newProd);
return newProd;
},
getDataStruct: function() {
return dataStruc;
},
getJsonUrls: function() {
return JSONurls;
},
loadJSON: function(url, cat, callback) {
var requestURL, request, JsonObj;
requestURL = url;
request = new XMLHttpRequest();
request.open("GET", requestURL);
request.responseType = "text";
request.send();
request.onload = function() {
JsonObj = JSON.parse(request.response);
dataStruc.allProducts[cat] = JsonObj[cat];
callback(cat);
};
},
};
})();
var UIcontroller = (function() {
var DomStrings = {
shopCatg: ".shop-catg",
productCont: ".product-container",
};
//public methods
return {
// function display the object based on the category based on the event target
displayObjectToPage: function(cat) {
var deafultHtml;
// 1. loop over the product category
dataController.getDataStruct().allProducts[cat].forEach(function(cur) {
deafultHtml =
'<div class="col-lg-4 col-md-6 col-sm-10">' +
'<img class="img-fluid" src="../img/' +
cur.type +
"/" +
cur.pics[0] +
'.jpg">' +
'<h6 class="text-center">' +
cur.name +
"</h6>" +
'<div class="text-center text-muted">' +
cur.price +
"</div>" +
"</div>";
document
.querySelector(DomStrings.productCont)
.insertAdjacentHTML("beforeend", deafultHtml);
});
},
getDomStrings: function() {
return DomStrings;
},
};
})();
var mainController = (function(UIctrl, dataCrtl) {
var setUpEvents = function() {
var doneLoading = false;
var DOM = UIctrl.getDomStrings();
document.querySelector(DOM.shopCatg).addEventListener("click", function() {
InitializeData(event, function(cat) {
UIcontroller.displayObjectToPage(cat);
});
});
};
InitializeData = function(event, callback) {
var category = event.target.textContent;
if (event.target.textContent === "bags") {
dataController.loadJSON(
dataController.getJsonUrls().bags,
category,
callback
);
} else if (event.target.textContent === "briefcases") {
dataController.loadJSON(dataController.getJsonUrls().bags, "briefcases");
} else if (event.target.textContent === "belts") {
dataController.loadJSON(dataController.getJsonUrls().bags, "belts");
} else {
dataController.loadJSON(dataController.getJsonUrls().bags, "accs");
}
};
displayObject = function() {};
return {
init: function() {
setUpEvents();
},
};
})(UIcontroller, dataController);
mainController.init();
I'm not sure, but I noticed this potential issue:
request.send();
request.onload = function() {
// ...
}
I believe when you call send, the request should start asynchronously. If the request comes back before onload is assigned, you might be seeing it get skipped. I haven't used XHR directly in years, though.
Normally you'd want to add the onload callback before calling send() to avoid this issue.
I also just noticed that you're missing the event in the arguments of the callback function here:
▽
document.querySelector(DOM.shopCatg).addEventListener("click", function() {
▽ event is undefined
InitializeData(event, function(cat) {
UIcontroller.displayObjectToPage(cat);
});
});

YouTube API - iframe onStateChange events

I'm using the iframe YouTube API and I want to track events, for example, sending data to google analytics, when user start and stop video.
<iframe src="https://www.youtube.com/embed/DjB1OvEYMhY"></iframe>
I looked https://developers.google.com/youtube/iframe_api_reference?csw=1 and did not find an example how to do that. The example creates iframe and defines onReady and onStateChange as well. How would I do same when I've only iframe on page?
This example listens to every play/pause action the user makes, using onPlayerStateChange with its different states, and prints (records) them.
However, you need to create your own record function to do whatever you want with this data.
You also need an ID on your iframe (#player in this case) and to add ?enablejsapi=1 at the end of its URL. And of course, make sure to include the Youtube iframe API.
Note
It's important to declare the API after your code, because it calls onYouTubeIframeAPIReady when it's ready.
<!DOCTYPE html>
<html>
<body>
<iframe id="player" src="https://www.youtube.com/embed/DjB1OvEYMhY?enablejsapi=1"></iframe>
<h5>Record of user actions:</h5>
<script>
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player( 'player', {
events: { 'onStateChange': onPlayerStateChange }
});
}
function onPlayerStateChange(event) {
switch(event.data) {
case 0:
record('video ended');
break;
case 1:
record('video playing from '+player.getCurrentTime());
break;
case 2:
record('video paused at '+player.getCurrentTime());
}
}
function record(str){
var p = document.createElement("p");
p.appendChild(document.createTextNode(str));
document.body.appendChild(p);
}
</script>
<script src="https://www.youtube.com/iframe_api"></script>
</body>
</html>
JS Fiddle Demo
Here is a version that doesn't use Youtubes iframe API script. The only drawback is that the iframe API might change.
<iframe id="player" src="https://www.youtube.com/embed/dQw4w9WgXcQ?enablejsapi=1"></iframe>
var addYoutubeEventListener = (function() {
var callbacks = [];
var iframeId = 0;
return function (iframe, callback) {
// init message listener that will receive messages from youtube iframes
if(iframeId === 0) {
window.addEventListener("message", function (e) {
if(e.origin !== "https://www.youtube.com" || e.data === undefined) return;
try {
var data = JSON.parse(e.data);
if(data.event !== 'onStateChange') return;
var callback = callbacks[data.id];
callback(data);
}
catch(e) {}
});
}
// store callback
iframeId++;
callbacks[iframeId] = callback;
var currentFrameId = iframeId;
// sendMessage to frame to start receiving messages
iframe.addEventListener("load", function () {
var message = JSON.stringify({
event: 'listening',
id: currentFrameId,
channel: 'widget'
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
message = JSON.stringify({
event: "command",
func: "addEventListener",
args: ["onStateChange"],
id: currentFrameId,
channel: "widget"
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
});
}
})();
addYoutubeEventListener(document.getElementById("player"), function(e) {
switch(e.info) {
case 1:
// playing
break;
case 0:
// ended
break;
}
});
Sometimes the event load is not enough to ensure that the document inside the iframe is ready. If the iframe is in a different domain it is not possible to subscribe to see when it is ready.
A possible workaround is to record when an event is received from the iframe, if after subscribing no event was received try again:
var addYoutubeEventListener = (function() {
var callbacks = [];
var iframeId = 0;
var subscribed = [];
return function (iframe, callback) {
// init message listener that will receive messages from youtube iframes
if(iframeId === 0) {
window.addEventListener("message", function (e) {
if(e.origin !== "https://www.youtube.com" || e.data === undefined) return;
try {
var data = JSON.parse(e.data);
subscribed[data.id] = true;
if(data.event !== 'onStateChange') return;
var callback = callbacks[data.id];
callback(data);
}
catch(e) {}
}, true);
}
// store callback
iframeId++;
callbacks[iframeId] = callback;
subscribed[iframeId] = false;
var currentFrameId = iframeId;
//console.log("adding event listener to iframe id " + iframeId);
// sendMessage to frame to start receiving messages
iframe.addEventListener("load", function () {
var tries = 0;
var checkSubscribed = function()
{
if (subscribed[currentFrameId]) {
//console.log("subscribed succesfully " + currentFrameId)
}
else
{
tries++;
//console.log("Try again " + currentFrameId + " (" + tries + ")");
if (tries < 100) {
doSubscribe();
}
else
{
console.log("Unable to subscribe" + currentFrameId );
}
}
}
var doSubscribe = function()
{
var message = JSON.stringify({
event: 'listening',
id: currentFrameId,
channel: 'widget'
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
message = JSON.stringify({
event: "command",
func: "addEventListener",
args: ["onStateChange"],
id: currentFrameId,
channel: "widget"
});
iframe.contentWindow.postMessage(message, 'https://www.youtube.com');
setTimeout(checkSubscribed, 100);
};
doSubscribe();
}, true);
}
})();

Page loading is gradually getting slower using jQuery script

I'm using this jQuery script to show search results. Everything works fine, but when search results have more than one page and I'm browsing pages via paging then every page loading is gradually getting slower. Usually first cca 10 pages loads I get quickly, but next are getting avoiding loading delay. Whole website get frozen for a little while (also loader image), but browser is not yet. What should be the problem?
function editResults(def) {
$('.searchResults').html('<p class=\'loader\'><img src=\'images/loader.gif\' /></p>');
var url = def;
var url = url + "&categories=";
// Parse Categories
$('input[name=chCat[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
url = url + "&sizes=";
// Parse Sizes
$('input[name=chSize[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
url = url + "&prices=";
// Parse Prices
$('input[name=chPrice[]]').each(function() {
if (this.checked == true) {
url = url + this.value + ",";
}
});
$('.searchResults').load('results.php'+url);
$('.pageLinks').live("click", function() {
var page = this.title;
editResults("?page="+page);
});
}
$(document).ready(function(){
editResults("?page=1");
// Check All Categories
$('input[name=chCat[0]]').click(function() {
check_status = $('input[name=chCat[0]]').attr("checked");
$('input[name=chCat[]]').each(function() {
this.checked = check_status;
});
});
// Check All Sizes
$('input[name=chSize[0]]').click(function() {
check_status = $('input[name=chSize[0]]').attr("checked");
$('input[name=chSize[]]').each(function() {
this.checked = check_status;
});
});
// Edit Results
$('.checkbox').change(function() {
editResults("?page=1");
});
// Change Type
$(".sort").change(function() {
editResults("?page=1&sort="+$(this).val());
});
});
$('.pageLinks').live("click", function() {
var page = this.title;
editResults("?page="+page);
});
just a wild guess but... wouldn't this piece of code add a new event handler to the click event instead reaplacing the old one with a new one? causing the click to call all the once registered handlers.
you should make the event binding just once
var global_var = '1';
function editResults(def) {
// all your code
global_var = 2; // what ever page goes next
};
$(document).ready(function() {
// all your code ...
$('.pageLinks').live("click", function() {
var page = global_var;
editResults("?page="+page);
});
});

Uncaught ReferenceError: X is not defined

This code is being used on a Chrome Extension.
When I call the "showOrHideYT()" function, I get a
"Uncaught ReferenceError: showOrHideYT is not defined | (anonymous
function) | onclick"
This code will search for youtube links in a page, and it will add a button (it's really a div with an event) next to the link to show the iframe with the embedded video, pretty much like Reddit Enhancement Suite. Consider the code, per se, incomplete. I just want to know what am i missing when i call the "showOrHideYT(frameZES12345)" function.
if needed, i can provide manifest.json.
Thanks
function showOrHideYT(id)
{
var YTvidWidth = 420;
var YTvidHeight = 315;
frameYT=getElementById(id);
console.log(frameYT.style.visibility);
if (frameYT.style.visibility == "hidden")
{
frameYT.style.width = YTvidWidth+"px";
frameYT.style.height = YTvidHeight+"px";
frameYT.style.visibility = "visible";
}
if (frameYT.style.visibility == "visible")
{
frameYT.style.width = "0px";
frameYT.style.height = "0px";
frameYT.style.visibility = "hidden";
}
};
// DOM utility functions
function insertAfter( referenceNode, newNode ) {
if ((typeof(referenceNode) == 'undefined') || (referenceNode == null)) {
console.log(arguments.callee.caller);
} else if ((typeof(referenceNode.parentNode) != 'undefined') && (typeof(referenceNode.nextSibling) != 'undefined')) {
if (referenceNode.parentNode == null) {
console.log(arguments.callee.caller);
} else {
referenceNode.parentNode.insertBefore( newNode, referenceNode.nextSibling );
}
}
};
function createElementWithID(elementType, id, classname) {
obj = document.createElement(elementType);
if (id != null) {
obj.setAttribute('id', id);
}
if ((typeof(classname) != 'undefined') && (classname != '')) {
obj.setAttribute('class', classname);
}
return obj;
};
///////////////////////////////////////
$(document).ready(function() {
var vidWidth = 420;
var vidHeight = 315;
var linksSemID = document.getElementsByTagName("a") ;
for (var i = 0; i < linksSemID.length; i++){
if (/id=$/.test(linksSemID[i].href)) links[i].href += "1";
}
i=0;
var youTubeRegExp = /(?:v=)([\w\-]+)/g;
var forEach = Array.prototype.forEach;
var linkArray = document.getElementsByTagName('a');
forEach.call(linkArray, function(link){
linkArray.id="zes" + i++;
var linkTarget = link.getAttribute('href');
if (linkTarget!=null)
{
if (linkTarget.search(youTubeRegExp) !=-1)
{
console.log (linkTarget);
idVideo=linkTarget.match(/(?:v=)([\w\-]+)/g);
//idVideo = idVideo.replace("v=", "");
//add buton
botaoMais = document.createElement('DIV');
botaoMais.setAttribute('class','expando-button collapsed video');
botaoMais.setAttribute('onclick','showOrHideYT(frameZES'+ i +')');
insertAfter(link, botaoMais);
//add iframe
ifrm = document.createElement('IFRAME');
ifrm.setAttribute('src', 'http://www.youtube.com/embed/'+ idVideo);
ifrm.style.width = '0px';
ifrm.style.height = '0px';
ifrm.style.frameborder='0px';
ifrm.style.visibility = 'hidden';
ifrm.setAttribute('id', 'frameZES' + i);
insertAfter(link, ifrm);
}
}
});
});
When you use setAttribute with a string, the event will be executed in the context of the page. The functions which are defined in a Content script are executed in a sandboxed scope. So, you have to pass a function reference, instead of a string:
Replace:
botaoMais.setAttribute('onclick','showOrHideYT(frameZES'+ i +')');
With:
botaoMais.addEventListener('click', (function(i) {
return function() {
showOrHideYT("frameZES"+ i);
};
})(i));
Explanation of code:
(function(i) { ..})(i) is used to preserve the value of i for each event.
Inside this self-invoking function, another function is returned, used as an event listener to click.
I see that you are using jQuery in your code. I personally think if we are using a library like jQuery, then we should not mix the native javascript code and jQuery code.
You can use jQuery bind to bind your the functions you need to call on dom ready.
Read below to know more.
suppose you want to call a javascript function on a button click, Here is the HTML for the same.
<div id="clickme">
<input id= "clickmebutton" type="button" value = "clickme" />
</div>
suppose "test" is the function you need to call, here is the code for test function.
function test() {
alert("hello");
}
you now need to bind the test function on the button click.
$(document).ready(function() {
$("#clickmebutton").bind("click", function(){
// do what ever you want to do here
test();
});
});

Categories