There is a function in js which displays messages to the table (messages are stored in json). In Google Chrome, it works, but Safari, Opera or Microsoft Edge - no!
There is a mistake in code which is associated with the call to setTimeout (callback, 5000)(nothing is sent to the callback).So, For (var i = 0; i <respond.length; i ++) will not work since respond === undefined.
But why is it so?
callback(
[{
"time": "1500303264",
"user": "qwe",
"message": "we",
"id": 1
},
{
"time": "1500303987",
"user": "Max",
"message": "q",
"id": 2
}
]);
function smile(mess) {
var smile = ":)";
var graficSmile = "<img src = './image/Smile.png' alt='Smile' align='middle'>";
var string_with_replaced_smile = mess.replace(smile, graficSmile);
var sad = ":("
var graficSad = "<img src = './image/Sad.png' alt='Smile' align='middle'>";
var string_with_replaced_smile_and_sad = string_with_replaced_smile.replace(sad, graficSad);
return string_with_replaced_smile_and_sad;
}
$.getJSON('data/messages.json', callback);
var exists = [];
function callback(respond) {
var timeNow = Date.now();
for (var i = 0; i < respond.length; i++) {
var data = respond[i];
if (exists.indexOf(data.id) != -1) continue;
var timeInMessage = data.time * 1000;
var diff_time = (timeNow - timeInMessage);
if (diff_time <= 3600000) {
var rowClone = $('.mess_hide').clone().removeClass('mess_hide');
var newDate = new Date(timeInMessage);
var dateArray = [newDate.getHours(), newDate.getMinutes(), newDate.getSeconds()]
var res = dateArray.map(function(x) {
return x < 10 ? "0" + x : x;
}).join(":");
$('#messages').append(rowClone);
$('.time', rowClone).html(res);
$('.name', rowClone).html(data.user);
$('.message', rowClone).html(smile(data.message));
$('.scroller').scrollTop($('#messages').height());
exists.push(data.id);
}
}
setTimeout(function(){callback(respond)}, 5000);
}
.scroller {
width: 490px;
height: 255px;
max-height: 255px;
overflow-y: auto;
overflow-x: hidden;
}
table#messages {
min-height: 260px;
width: 100%;
background: #fffecd;
border: none;
}
table#messages::-webkit-scrollbar {
width: 1em;
}
table#messages::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
table#messages::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
tr {
height: 20%;
display: block;
}
td.time,
td.name {
width: 70px;
max-width: 75px;
text-align: center;
}
td.name {
font-weight: bold;
}
form#text_submit {
display: inline-flex;
align-items: flex-start;
}
input#text {
width: 370px;
height: 30px;
margin-top: 20px;
background: #fffecd;
font-family: 'Montserrat';
font-size: 16px;
border: none;
align-self: flex-start;
}
input#submit {
padding: 0;
margin-left: 21px;
margin-top: 21px;
height: 30px;
width: 95px;
background: #635960;
border: none;
color: white;
font-family: 'Montserrat';
font-size: 16px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="scroller">
<table id="messages">
<tr class="mess_hide">
<td class="time"></td>
<td class="name"></td>
<td class="message"></td>
</tr>
</table>
</div>
<form method="POST" id="easyForm">
<input type="text" name="text" id="text">
<input type="submit" value="Send" id="submit">
</form>
</div>
Chrome
Opera
Since it is assumed that the var exists - array, but the value of the array ([]) is assigned to it only later, after the call $.getJSON(...). So, when callback is called for the first time value [] is not set for exists.We just need to move var exists above the first call of callback.
When callback is called by the timer, nothing is passed to it. But timer needs to reread the messages from the file and display them on the screen.So, instead setTimeout(function(){callback(respond)}, 5000); we need setTimeout(function(){$.getJSON('data/messages.json', callback);}, 5000);.
var exists = [];
$.getJSON('data/messages.json', callback);
function callback(respond) {
var timeNow = Date.now();
for (var i = 0; i < respond.length; i++) {
var data = respond[i];
if (exists.indexOf(data.id) != -1) continue;
var timeInMessage = data.time * 1000;
var diff_time = (timeNow - timeInMessage);
if (diff_time <= 3600000) {
var rowClone = $('.mess_hide').clone().removeClass('mess_hide');
var newDate = new Date(timeInMessage);
var dateArray = [newDate.getHours(), newDate.getMinutes(), newDate.getSeconds()]
var res = dateArray.map(function(x) {
return x < 10 ? "0" + x : x;
}).join(":");
$('#messages').append(rowClone);
$('.time', rowClone).html(res);
$('.name', rowClone).html(data.user);
$('.message', rowClone).html(smile(data.message));
$('.scroller').scrollTop($('#messages').height());
exists.push(data.id);
}
}
setTimeout(function() {
$.getJSON('data/messages.json', callback);
}, 5000);
}
Since callback requires an array to be passed as an argument, setTimeout must ensure that when it calls callback, it passes the array.
Change
setTimeout(callback, 5000);
to
setTimeout(function(){callback(respond)}, 5000);
which allows callback to be called with an argument as the body of an anonymous function that will be called by setTimeout.
Also, as a side note, if you used respond.forEach() instead of a counting for loop, the code would be much cleaner:
respond.forEach(function(data) {
if (exists.indexOf(data.id) != -1) continue;
var timeInMessage = data.time * 1000;
var diff_time = (timeNow - timeInMessage);
if (diff_time <= 3600000) {
var rowClone = $('.mess_hide').clone().removeClass('mess_hide');
var newDate = new Date(timeInMessage);
var dateArray = [newDate.getHours(), newDate.getMinutes(), newDate.getSeconds()]
var res = dateArray.map(function(x) {
return x < 10 ? "0" + x : x;
}).join(":");
$('#messages').append(rowClone);
$('.time', rowClone).html(res);
$('.name', rowClone).html(data.user);
$('.message', rowClone).html(smile(data.message));
$('.scroller').scrollTop($('#messages').height());
exists.push(data.id);
}
});
Related
I have a graph that is rendering its values as a div inside the body element with a class according to their number values. This is working fine. But next I need to sort the divs according to their number values or background color. BUT, it needs to start on the lower left corner of the page and fan out upwards to towards the right as the numbers increase. Basically just like a line graph.
I'd like to stay away from libraries if at all possible.
How would I approach this? Thank you all.
let interval = setInterval(makeDivs, 5);
function makeDivs(){
let cont = checkHeight();
if(cont){
let div = document.createElement('div');
let randNum = Math.random() * 100;
if(randNum < 20) { div.classList.add('blue') }
if(randNum >= 20 && randNum < 40) { div.classList.add('green') }
if(randNum >= 40 && randNum < 60) { div.classList.add('yellow') }
if(randNum >= 60 && randNum < 80) { div.classList.add('orange') }
if(randNum >= 80 && randNum < 101) { div.classList.add('red') }
div.textContent = randNum.toFixed(2);
document.querySelector('body').appendChild(div);
} else {
alert('done');
clearInterval(interval);
sortDivs(); // Begin sorting divs
}
}
function checkHeight(){
let w = window.innerHeight;
let b = document.querySelector('body').offsetHeight;
if(b < w) {
return true;
} else {
return false;
}
}
function sortDivs(){
document.querySelector("body div:last-child").remove();
alert('sorting now...')
}
* { box-sizing: border-box;}
body { width: 100vw; margin: 0; padding: 0; display: flex; flex-wrap: wrap; align-items: end;}
body div { width: calc(10% + 1px); text-align: center; border: 1px solid #ddd; margin: -1px 0 0 -1px; padding: 10px;}
body div.blue { background: aqua; }
body div.green { background: green; }
body div.yellow { background: yellow; }
body div.orange { background: orange; }
body div.red { background: red; }
UPDATE!!!
So I have this so far based on the feed back down below. The problem now is the sorting is only happening laterally and not on an angle (spreading right and to the top).
let interval = setInterval(makeDivs, 10);
function makeDivs(){
let cont = checkHeight();
if(cont){
let div = document.createElement('div');
let randNum = Math.random() * 100;
if(randNum < 20) { div.classList.add('blue') }
if(randNum >= 20 && randNum < 40) { div.classList.add('green') }
if(randNum >= 40 && randNum < 60) { div.classList.add('yellow') }
if(randNum >= 60 && randNum < 80) { div.classList.add('orange') }
if(randNum >= 80 && randNum < 101) { div.classList.add('red') }
div.textContent = randNum.toFixed(2);
document.querySelector('.outPut').appendChild(div);
} else {
clearInterval(interval);
document.querySelector(".outPut div:last-child").remove();
compileArrays(); // Begin sorting divs
}
}
function checkHeight(){
let w = window.innerHeight;
let b = document.querySelector('.outPut').offsetHeight;
if(b < w) {
return true;
} else {
return false;
}
}
function compileArrays(){
let divs = document.querySelectorAll('.outPut div');
let bArr = [], gArr = [], yArr = [], oArr = [], rArr = [];
divs.forEach( (d) => {
if( d.classList.contains('blue') ){ bArr.push(d) }
if( d.classList.contains('green') ){ gArr.push(d) }
if( d.classList.contains('yellow') ){ yArr.push(d) }
if( d.classList.contains('orange') ){ oArr.push(d) }
if( d.classList.contains('red') ){ rArr.push(d) }
});
let finalArr = sortArray(bArr).concat(sortArray(gArr)).concat(sortArray(yArr)).concat(sortArray(oArr)).concat(sortArray(rArr));
newDom(finalArr);
}
function sortArray(arr){
let newArr = arr;
newArr.sort( (a, b) => {
return a.innerText - b.innerText;
});
return newArr;
}
function newDom(arr){
let b = document.querySelector('.outPut');
b.innerHTML = '';
arr.reverse();
arr.forEach((a) => {
b.appendChild(a);
});
}
* { box-sizing: border-box;}
body { width: 100vw; height: 100vh; margin: 0; padding: 0; display: flex; align-items: flex-end;}
body .outPut { flex: 1; display: flex; flex-wrap: wrap; flex-direction:row-reverse; }
body .outPut div { width: calc(10% + 1px); text-align: center; border: 1px solid #ddd; margin: -1px 0 0 -1px; padding: 10px;}
body .outPut div.blue { background: aqua; }
body .outPut div.green { background: #44df15; }
body .outPut div.yellow { background: yellow; }
body .outPut div.orange { background: orange; }
body .outPut div.red { background: red; }
<div class="outPut"></div>
Supposed you already have a mechanism to organise such DIVs in a grid as shown, the following should give you what you are looking for:
var items = divList.filter((div) => div.nodeType == 1); // get rid of the whitespace text nodes
items.sort(function(a, b) {
return a.innerHTML == b.innerHTML
? 0
: (a.innerHTML > b.innerHTML ? 1 : -1);
});
Then, place them back in the DOM as needed, example:
for (i = 0; i < items.length; ++i) {
divList.appendChild(items[i]);
}
This worked with the first code example!!!
try this sortDivs function:
function sortDivs() {
document.querySelector("body div:last-child").remove();
alert('sorting now...')
let toSort = document.getElementsByTagName("div")
toSort = Array.prototype.slice.call(toSort, 0)
toSort.sort((a, b) => {
let aord = parseFloat(a.textContent);
let bord = parseFloat(b.textContent);
return bord - aord;
})
document.body.innerHTML = ""
for(var i = 0, l = toSort.length; i < l; i++) {
document.querySelector('body').appendChild(toSort[i]);
}
}
and in the css file set flex-wrap to wrap-reverse. Hope I could help :)
PS: please, implement some else if instead of doing only if
Here is a small fiddle with my sample code demonstrating a simple solution in pure JavaScript and absolute CSS positioning for what you are trying to achieve. Link
As some pointed out already, there might be a library, that already provides a better and complete solution for this - I did not research if it is so.
Code:
file.js
var container = document.getElementById("container")
var results = [1,2,3,4,5,6,7,8]
//you can pre-calculate the order of the distances
//here already orderdered array [distanec][X-axis][Y-axis]
var distances =[[0,0,0],
[1,1,0],
[1,0,1],
[1.414, 1,1],
[2,0,2],
[2,2,0],
[2.234, 2,1],
[2.234, 1,2]]
for (i = 0; i < results.length; i++){
var newDiv = document.createElement("div")
newDiv.className = "result"
newDiv.innerHTML = results[i]
newDiv.style.left = distances[i][1]*20 + "px"
newDiv.style.bottom = distances[i][2]*20 + "px"
container.appendChild(newDiv)
}
function setColor(element){
// set class based on value - you already have this part
}
style.css
#container {
border: 4px;
border-color: red;
border-style: solid;
height: 200px;
width: 200px;
position: relative;
}
.result{
border: 2px;
width: 20px;
height: 20px;
position: absolute;
border-color: blue;
border-style: solid;
text-align: center;
}
site.html
<div id="container">
</div>
Output:
I've built a small balloon game , trying to learn the foundations,
anyways I've successfully rendered balloons, i can see them both on the page however the move functions doesn't work,it should do so on page load that I've set in the index.html body(the startGame func) , code looks fine,however nothing happens.
var gNextId = 100
var gBaloons = createBalloons()
var gInterval = null
function startGame() {
renderBaloons();
gInterval = setInterval(() => {
moveBalloons();
}, 500);
}
function renderBaloons() {
var strHtml = ''
for (var i = 0; i < gBaloons.length; i++) {
var balloon = gBaloons[i]
strHtml += `
${balloon.txt}
`
}
document.querySelector('.balloon-container').innerHTML = strHtml
}
function moveballoons() {
var elballoons = document.querySelectorAll('.balloon')
for (var i = 0; i > gBaloons.length; i++) {
var balloon = gBaloons[i]
var elballoon = elballoons[i]
balloon.bottom += balloon.speed
elballoon.style.bottom = balloon.bottom + 'px'
}
if (balloon.bottom >= 800) clearInterval(gInterval)
}
function createBalloons() {
var ballons = [
createBalloon('A'),
createBalloon('B'),
createBalloon('C')
];
return ballons
}
function createBalloon(txt) {
return {
id: gNextId++,
bottom: 0,
speed: 45,
txt: txt
}
}
body {
background-color: lightblue;
}
.balloon {
position: absolute;
width: 170px;
height: 200px;
border-radius: 40%;
bottom: 0;
transition: 3s;
background-color: yellow;
text-align: center;
font-weight: bold;
}
.balloon1 {
left: 200px;
background-color: rgb(3, 61, 23);
}
.balloon2 {
left: 600px;
background-color: rgb(182, 24, 50);
}
.fade {
opacity: 0;
}
In the moveballoons() function the following line:
for (var i = 0; i > gBaloons.length; i++) {
Should be
for (var i = 0; i < gBaloons.length; i++) {
Note: I've swapped > with <
I couldn't the get example running, so there could well be other reasons as to why this is not working but that is definitely an issue.
I need some help or advice I am trying to limit the random number to only generate 20 out of the 50 numbers and this activated when a button is pressed/clicked sometimes i get duplicates too which i would to stop happening
function lottoNumbers()
{
var lottoNums = [];
for(var i=0; i <1 ; i++)
{
var temp = Math.floor(Math.random() *50);
if(lottoNums.indexOf(temp) == -1)
{
lottoNums.push(temp);
document.getElementById('circle'+i).innerHTML = lottoNums[i];
}
else
{
i--;
}
}
}
Rather than using a for loop - use a while loop (ie - while length < 20) and as you are doing - only push the numbers into it if they are not already there.
For demo purposes- i am building a list dynamically and then styling the li's to give a rounded shape with border-radius;
lottoNumbers();
function lottoNumbers() {
var lottoNums = [];
var lottoNumStr = '';
while(lottoNums.length <20) {
var temp = Math.floor(Math.random() *50);
if(lottoNums.indexOf(temp) == -1) {
lottoNums.push(temp);
lottoNumStr += '<li>' + temp + '</li>';
}
}
document.getElementById('lottoNumList').innerHTML = lottoNumStr;
}
#lottoNumList {
list-style: none;
}
#lottoNumList li {
border: solid 1px #d4d4d4;
border-radius: 50%;
display: inline-block;
margin: 5px;
padding: 4px;
width: 30px;
height: 30px;
line-height: 30px;
text-align: center;
}
<h1>Lotto Numbers</h1>
<ul id ="lottoNumList"></ul>
Please I need I help on how to implement something like this responsively using bootstrap
html code for is displayed below
<div class="wrapper">
<div class="container">
<div class="special">
<div id="counter">
<div id="shading"></div>
</div>
</div>
</div>
please below is the css file for the above html code
.special
{
position: relative;
width: 840px;
height: 247px;
background-image: url('https://www.jqueryscript.net/demo/Colorful-Countdown-Timer/images/special_offer_bg.png');
background-position: -10px 74px;
background-repeat: no-repeat;
}
#counter
{
position: absolute;
top: 135px;
left: 279px;
z-index: 4000;
}
.digit-separator
{
position: relative;
float: left;
width: 17px;
height: 44px;
overflow: hidden;
background-image: url('https://www.jqueryscript.net/demo/Colorful-Countdown-Timer/images/digit_separator.png');
background-repeat: no-repeat;
background-position: 0px 0px;
}
.digit
{
background-image: url('https://www.jqueryscript.net/demo/Colorful-Countdown-Timer/images/digits.png');
}
#shading
{
background-image: url('https://www.jqueryscript.net/demo/Colorful-Countdown-Timer/images/sprites.png');
background-position: 0px -396px;
background-repeat: repeat-x;
float: left;
height: 44px;
position: absolute;
width: 291px;
z-index: 4100;
top: 0;
left: 0;
}
please this is the JavaScript code for the above html code
function C3Counter(id, opt) {
this.options = {
stepTime: 60, // not used
format: "dd:hh:mm:ss", // not used
startTime: "01:04:40:59",
digitImages: 1,
digitWidth: 30,
digitHeight: 44,
digitSlide : true,
digitSlideTime : 200,
digitImageHeight : 484,
digitAnimationHeight : 44,
timerEnd: function(){},
image: "digits.png",
updateInterval : 1000
};
var s;
if (typeof opt != "undefined") {
for (s in this.options) {
if (typeof opt[s] != "undefined") {
this.options[s] = opt[s];
}
}
}
if (String(options.startTime).indexOf(":") == -1) {
options.tempStartTime = options.startTime;
} else {
//TODO - does not convert time with : to seconds to count
var td = new Date(options.startTime);
}
this.pad2 = function(number) {
return (number < 10 ? '0' : '') + number;
}
var timer = setInterval( "this.updateCounter()", options.updateInterval);
var startTime = new Date().getTime();
var secNo = 0;
var timerSingle = new Array();
var dc = 0;
var digits = new Array();
var d = new Date();
var lastTime = d.getTime();
this.calculateTime = function() {
var tempTime = options.tempStartTime;
if (String(options.tempStartTime).indexOf(":") == -1) {
var seconds=Math.round(options.tempStartTime % 60);
options.tempStartTime=Math.floor(options.tempStartTime/60);
var minutes=Math.round(options.tempStartTime % 60);
options.tempStartTime=Math.floor(options.tempStartTime/60);
var hours=Math.round(options.tempStartTime % 24);
options.tempStartTime=Math.floor(options.tempStartTime/24);
var days=Math.round(options.tempStartTime);
options.timeStr = this.pad2(days)+this.pad2(hours)+this.pad2(minutes)+this.pad2(seconds);
}
var currTime = new Date().getTime();
var diff = currTime - startTime;
options.tempStartTime = options.startTime - Math.round(diff/1000);
}
this.calculateTime();
for (dc=0; dc<8; dc++) {
digits[dc] = { digit: this.options.timeStr.charAt(dc)};
$("#"+id).append("<div id='digit"+dc+"' style='position:relative;float:left;width:"+this.options.digitWidth+"px;height:"+this.options.digitHeight+"px;overflow:hidden;'><div class='digit' id='digit-bg"+dc+"' style='position:absolute; top:-"+digits[dc].digit*this.options.digitAnimationHeight+"px; width:"+this.options.digitWidth+"px; height:"+this.options.digitImageHeight+"px; '></div></div>");
if (dc % 2 == 1 && dc < 6) {
$("#"+id).append("<div class='digit-separator' style='float:left;'></div>");
}
}
$("#"+id).append("<div style='clear:both'></div>");
this.animateDigits = function() {
for (var dc=0; dc<8; dc++) {
digits[dc].digitNext = Number(this.options.timeStr.charAt(dc));
digits[dc].digitNext = (digits[dc].digitNext + 10)%10;
var no = dc;
if (digits[no].digit == 0) $("#digit-bg"+no).css("top", -this.options.digitImageHeight+this.options.digitHeight + "px");
if (digits[no].digit != digits[no].digitNext) {
$("#digit-bg"+no).animate( { "top" : -digits[no].digitNext*options.digitHeight+"px"}, options.digitSlideTime);
digits[no].digit = digits[no].digitNext;
}
}
var end = this.checkEnd();
}
this.checkEnd = function() {
for (var i = 0; i < digits.length; i++) {
if (digits[i].digit != 0) {
return false;
}
}
clearInterval(timer);
this.options.timerEnd();
return true;
}
this.updateCounter = function() {
d = new Date();
if ((d.getTime() - lastTime) < (options.updateInterval - 50)) {
return;
}
lastTime = d.getTime();
this.calculateTime();
this.animateDigits();
}
}
C3Counter("counter", { startTime :16100 });
Note* you need to use Bootstrap v3.3.4 or higher and jQuery v2.1.3 or higher
Note* This doesnt look like the exact example you linked to. Its not posible to achieve that with default boostrap library.
html:
<div class="wrapper">
<div class="special">
<span id="clock"></span>
</div>
</div>
js:
$('#clock').countdown('2020/10/10', function(event) {
$(this).html(event.strftime('%D days %H:%M:%S'));
});
css:
.wrapper
{
width: 100%;
height: auto;
background-color: #dc403b;
}
.special
{
width:100%;
background-image: url('https://www.jqueryscript.net/demo/Colorful-Countdown-Timer/images/special_offer_bg.png');
background-position: cover;
background-repeat: no-repeat;
text-align: center;
padding: 50px 0;
font-size: 18px;
}
In my AngularJS application I am attempting to add a Facebook Native Web Ad to one of my views. I followed the steps outlined in the documentation to generate the necessary HTML snippet and added this to my view.
My application is using ui-router to resolve routes. When visiting the route/view containing this code snippet the FB ad is not displayed and neither of the callbacks are invoked (onAdLoaded or onAdError).
Facebook Generated HTML Snippet (added to view):
<div style="display:none; position: relative;">
<iframe style="display:none;"></iframe>
<script type="text/javascript">
var data = {
placementid: 'xxxxxxxxxxx_xxxxxxxx',
format: 'native',
testmode: true,
onAdLoaded: function (element) {
console.log('Audience Network ad loaded');
element.style.display = 'block';
},
onAdError: function (errorCode, errorMessage) {
console.log('Audience Network error (' + errorCode + ') ' + errorMessage);
}
};
(function (w, l, d, t) {
var a = t();
var b = d.currentScript || (function () {
var c = d.getElementsByTagName('script');
return c[c.length - 1];
})();
var e = b.parentElement;
e.dataset.placementid = data.placementid;
var f = function (v) {
try {
return v.document.referrer;
} catch (e) {
}
return '';
};
var g = function (h) {
var i = h.indexOf('/', h.indexOf('://') + 3);
if (i === -1) {
return h;
}
return h.substring(0, i);
};
var j = [l.href];
var k = false;
var m = false;
if (w !== w.parent) {
var n;
var o = w;
while (o !== n) {
var h;
try {
m = m || (o.$sf && o.$sf.ext);
h = o.location.href;
} catch (e) {
k = true;
}
j.push(h || f(n));
n = o;
o = o.parent;
}
}
var p = l.ancestorOrigins;
if (p) {
if (p.length > 0) {
data.domain = p[p.length - 1];
} else {
data.domain = g(j[j.length - 1]);
}
}
data.url = j[j.length - 1];
data.channel = g(j[0]);
data.width = screen.width;
data.height = screen.height;
data.pixelratio = w.devicePixelRatio;
data.placementindex = w.ADNW && w.ADNW.Ads ? w.ADNW.Ads.length : 0;
data.crossdomain = k;
data.safeframe = !!m;
var q = {};
q.iframe = e.firstElementChild;
var r = 'https://www.facebook.com/audiencenetwork/web/?sdk=5.3';
for (var s in data) {
q[s] = data[s];
if (typeof(data[s]) !== 'function') {
r += '&' + s + '=' + encodeURIComponent(data[s]);
}
}
q.iframe.src = r;
q.tagJsInitTime = a;
q.rootElement = e;
q.events = [];
w.addEventListener('message', function (u) {
if (u.source !== q.iframe.contentWindow) {
return;
}
u.data.receivedTimestamp = t();
if (this.sdkEventHandler) {
this.sdkEventHandler(u.data);
} else {
this.events.push(u.data);
}
}.bind(q), false);
q.tagJsIframeAppendedTime = t();
w.ADNW = w.ADNW || {};
w.ADNW.Ads = w.ADNW.Ads || [];
w.ADNW.Ads.push(q);
w.ADNW.init && w.ADNW.init(q);
})(window, location, document, Date.now || function () {
return +new Date;
});
</script>
<script type="text/javascript" src="https://connect.facebook.net/en_US/fbadnw.js" async></script>
<style>
.thirdPartyRoot {
background-color: white;
color: #444;
border: 1px solid #ccc;
border-left: 0;
border-right: 0;
font-family: sans-serif;
font-size: 14px;
line-height: 16px;
width: 320px;
text-align: left;
position: relative;
}
.thirdPartyMediaClass {
width: 320px;
height: 168px;
margin: 12px 0;
}
.thirdPartySubtitleClass {
font-size: 18px;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
height: 16px;
-webkit-box-orient: vertical;
}
.thirdPartyTitleClass {
padding-right: 12px;
line-height: 18px;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
height: 36px;
-webkit-box-orient: vertical;
}
.thirdPartyCallToActionClass {
background-color: #416BC4;
color: white;
border-radius: 4px;
padding: 6px 20px;
float: right;
text-align: center;
text-transform: uppercase;
font-size: 11px;
}
.fbDefaultNativeAdWrapper {
font-size: 12px;
line-height: 14px;
margin: 12px 0;
height: 36px;
vertical-align: top;
}
</style>
<div class="thirdPartyRoot">
<a class="fbAdLink">
<div class="fbAdMedia thirdPartyMediaClass"></div>
<div class="fbAdSubtitle thirdPartySubtitleClass"></div>
<div class="fbDefaultNativeAdWrapper">
<div class="fbAdCallToAction thirdPartyCallToActionClass"></div>
<div class="fbAdTitle thirdPartyTitleClass"></div>
</div>
</a>
</div>
</div>
I noticed that the Facebook Audience Network JS is loaded asynchronously and suspected that I might have a race condition causing the issue.
<script type="text/javascript" src="https://connect.facebook.net/en_US/fbadnw.js" async></script>
To test this, I've moved the FB code snippet out of my view and into my SPA index.html. The ad appears as expected. What callback does the fbadnw.js script call once the script is loaded? Is the closure within the FB generated code being invoked before fbadnw.js is loaded perhaps?
index.html (works)
<!DOCTYPE html>
<html ng-app="kcl-app">
<head>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title ng-bind="pageTitle"></title>
</head>
<body>
<!-- ui-router view -->
<div ui-view></div>
<!-- FB Begin -->
<div class="fb-native">
<div style="display:none; position: relative;">
<iframe style="display:none;"></iframe>
<script type="text/javascript">
var data = {
placementid: 'xxxxxxxxxxx_xxxxxxxx',
format: 'native',
testmode: true,
onAdLoaded: function (element) {
console.log('Audience Network ad loaded');
element.style.display = 'block';
},
onAdError: function (errorCode, errorMessage) {
console.log('Audience Network error (' + errorCode + ') ' + errorMessage);
}
};
(function (w, l, d, t) {
var a = t();
var b = d.currentScript || (function () {
var c = d.getElementsByTagName('script');
return c[c.length - 1];
})();
var e = b.parentElement;
e.dataset.placementid = data.placementid;
var f = function (v) {
try {
return v.document.referrer;
} catch (e) {
}
return '';
};
var g = function (h) {
var i = h.indexOf('/', h.indexOf('://') + 3);
if (i === -1) {
return h;
}
return h.substring(0, i);
};
var j = [l.href];
var k = false;
var m = false;
if (w !== w.parent) {
var n;
var o = w;
while (o !== n) {
var h;
try {
m = m || (o.$sf && o.$sf.ext);
h = o.location.href;
} catch (e) {
k = true;
}
j.push(h || f(n));
n = o;
o = o.parent;
}
}
var p = l.ancestorOrigins;
if (p) {
if (p.length > 0) {
data.domain = p[p.length - 1];
} else {
data.domain = g(j[j.length - 1]);
}
}
data.url = j[j.length - 1];
data.channel = g(j[0]);
data.width = screen.width;
data.height = screen.height;
data.pixelratio = w.devicePixelRatio;
data.placementindex = w.ADNW && w.ADNW.Ads ? w.ADNW.Ads.length : 0;
data.crossdomain = k;
data.safeframe = !!m;
var q = {};
q.iframe = e.firstElementChild;
var r = 'https://www.facebook.com/audiencenetwork/web/?sdk=5.3';
for (var s in data) {
q[s] = data[s];
if (typeof(data[s]) !== 'function') {
r += '&' + s + '=' + encodeURIComponent(data[s]);
}
}
q.iframe.src = r;
q.tagJsInitTime = a;
q.rootElement = e;
q.events = [];
w.addEventListener('message', function (u) {
if (u.source !== q.iframe.contentWindow) {
return;
}
u.data.receivedTimestamp = t();
if (this.sdkEventHandler) {
this.sdkEventHandler(u.data);
} else {
this.events.push(u.data);
}
}.bind(q), false);
q.tagJsIframeAppendedTime = t();
w.ADNW = w.ADNW || {};
w.ADNW.Ads = w.ADNW.Ads || [];
w.ADNW.Ads.push(q);
w.ADNW.init && w.ADNW.init(q);
})(window, location, document, Date.now || function () {
return +new Date;
});
</script>
<script type="text/javascript" src="https://connect.facebook.net/en_US/fbadnw.js" async></script>
<style>
.thirdPartyRoot {
background-color: white;
color: #444;
border: 1px solid #ccc;
border-left: 0;
border-right: 0;
font-family: sans-serif;
font-size: 14px;
line-height: 16px;
width: 320px;
text-align: left;
position: relative;
}
.thirdPartyMediaClass {
width: 320px;
height: 168px;
margin: 12px 0;
}
.thirdPartySubtitleClass {
font-size: 18px;
-webkit-line-clamp: 1;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
height: 16px;
-webkit-box-orient: vertical;
}
.thirdPartyTitleClass {
padding-right: 12px;
line-height: 18px;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
height: 36px;
-webkit-box-orient: vertical;
}
.thirdPartyCallToActionClass {
background-color: #416BC4;
color: white;
border-radius: 4px;
padding: 6px 20px;
float: right;
text-align: center;
text-transform: uppercase;
font-size: 11px;
}
.fbDefaultNativeAdWrapper {
font-size: 12px;
line-height: 14px;
margin: 12px 0;
height: 36px;
vertical-align: top;
}
</style>
<div class="thirdPartyRoot">
<a class="fbAdLink">
<div class="fbAdMedia thirdPartyMediaClass"></div>
<div class="fbAdSubtitle thirdPartySubtitleClass"></div>
<div class="fbDefaultNativeAdWrapper">
<div class="fbAdCallToAction thirdPartyCallToActionClass"></div>
<div class="fbAdTitle thirdPartyTitleClass"></div>
</div>
</a>
</div>
</div>
</div>
<!-- FB End -->
</body>
</html>
I was able to resolve this issue by editing the boilerplate code provided by FB. In a nutshell, the provided closure (minified) attempts to:
Locate the iframe element where the Ad will be rendered and set it's
src and other attributes.
Attach an event handler to listen for post messages "message".
Initialize the Ad with FB Audience Network (ADNW.init())
My problem was with the assumptions this code makes in Step 1.
var b = d.currentScript || (function() {
var c = d.getElementsByTagName('script');
return c[c.length - 1];
})();
var e = b.parentElement;
The above code is attempting to locate this DIV.
<div style="display:none; position: relative;">
It does this by first trying to locate the parent of the last script element on the page. This is brittle and, in my case, did not work. The last script element added in my document was not the one this code expected.
I modified this code to explicitly select the correct element by ID.
Added an ID to containing DIV:
<div id="fb-ad-container" style="display:none; position: relative;">
Simplify the DOM parsing code (step 1) to select this DIV by ID:
var e = d.getElementById("fb-ad-container");
By selecting the correct element by ID I was able to alleviate the need to locate the current script element. The rest of the script ran as expected.