Today lets learn how to code the clickable area of Facebook Native Ad Unit. Before proceeding with today’s tutorial, please visit yesterdays tutorial and learn how to request and display the Native Ad creative inside Ionic 2 application.
Facebook Native Ads: Ionic 2
In todays video tutorial lets learn:
1. Removing top left corner green patch – clickable area problem.
2. Aligning the clickable area to the Native Ad Unit creative properly.
3. Make sure the top left corner clickable area doesn’t appear once again when we change the device orientation.
4. Removing clickable area once the user switches to other views.
5. Why doesn’t your app show Facebook Native Ads all the time – why is fill rate low?
Audience Network’s Native Ads Clickable Area: Ionic2
[youtube https://www.youtube.com/watch?v=7Zfe40dbjgs]
View Code: src/pages/home/home.html
< ion-card id="native">
< img src="" id="adCover" style="max-width: 100%; height: auto;" />
< ion-card-content text-wrap>
< ion-item>
< ion-avatar item-left>
< img src="" id="adIcon" />
< /ion-avatar>
< h2 id="adTitle">
< p id="adBody">
< /ion-item>
< ion-row>
< ion-col center text-left width-70 small style="font-size: x-small;">
< span id="adSocialContext" small>< /span>
< /ion-col>
< ion-col center text-right width-30>
< button ion-button small color="secondary" id="adBtn">
< /button>
< /ion-col>
< /ion-row>
< /ion-card-content>
< /ion-card> |
< ion-card id="native"> < img src="" id="adCover" style="max-width: 100%; height: auto;" /> < ion-card-content text-wrap> < ion-item> < ion-avatar item-left> < img src="" id="adIcon" /> < /ion-avatar> < h2 id="adTitle"> < p id="adBody"> < /ion-item> < ion-row> < ion-col center text-left width-70 small style="font-size: x-small;"> < span id="adSocialContext" small>< /span> < /ion-col> < ion-col center text-right width-30> < button ion-button small color="secondary" id="adBtn"> < /button> < /ion-col> < /ion-row> < /ion-card-content> < /ion-card>
Here am adding a simple ion-card item with cover image and ion-avatar. We’ve added id’s to each of the elements and will fill the value once the ad data is emitted by onAdLoaded event.
updateXY() method src/pages/home/home.ts
updateXY(left, top){
var h, w, x, y;
var d = document.getElementById('native');
w = d.clientWidth;
h = d.clientHeight;
x = d.offsetLeft - left;
y = d.offsetTop - top;
if(FacebookAds)
FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h);
}; |
updateXY(left, top){ var h, w, x, y; var d = document.getElementById('native'); w = d.clientWidth; h = d.clientHeight; x = d.offsetLeft - left; y = d.offsetTop - top; if(FacebookAds) FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h); };
Here we dynamically fetch the ad display containers width and height, and then x and y axis values of the display component.
When we scroll the application vertically only y value changes and x value remains constant, when we scroll the view horizontally x value changes and y value remains constant. In most of the application scrolling will only be available vertically.
Finally, we pass x, y, w, h value to setNativeAdClickArea() method along with the Native Ad Units ID as its first parameter.
src/pages/home/home.ts
import { Component} from '@angular/core';
import { NavController } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
constructor(public navCtrl: NavController, public platform: Platform) {
platform.ready().then(() => {
});
};
updateXY(left, top){
var d;
var h, w, x, y;
d = document.getElementById('native');
w = document.getElementById('native').clientWidth;
h = document.getElementById('native').clientHeight;
x = d.offsetLeft - left;
y = d.offsetTop - top;
if(FacebookAds)
FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h);
};
ionViewDidEnter(){
this.platform.ready().then(() => {
if(FacebookAds)
{
FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){
document.addEventListener("onAdLoaded", function(data){
let temp: any = data;
if(temp.adType == "native")
{
document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url));
document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url));
document.getElementById('adTitle').innerHTML = temp.adRes.title;
document.getElementById('adBody').innerHTML = temp.adRes.body;
document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext;
document.getElementById('adBtn').innerHTML = temp.adRes.buttonText;
}
});
}, function(err) { alert(JSON.stringify(err)); });
}
});
};
} |
import { Component} from '@angular/core';
import { NavController } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({ selector: 'page-home', templateUrl: 'home.html'
})
export class HomePage { constructor(public navCtrl: NavController, public platform: Platform) { platform.ready().then(() => { }); }; updateXY(left, top){ var d; var h, w, x, y; d = document.getElementById('native'); w = document.getElementById('native').clientWidth; h = document.getElementById('native').clientHeight; x = d.offsetLeft - left; y = d.offsetTop - top; if(FacebookAds) FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h); }; ionViewDidEnter(){ this.platform.ready().then(() => { if(FacebookAds) { FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){ document.addEventListener("onAdLoaded", function(data){ let temp: any = data; if(temp.adType == "native") { document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url)); document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url)); document.getElementById('adTitle').innerHTML = temp.adRes.title; document.getElementById('adBody').innerHTML = temp.adRes.body; document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext; document.getElementById('adBtn').innerHTML = temp.adRes.buttonText; } }); }, function(err) { alert(JSON.stringify(err)); }); } }); };
}
Now we need to access the view scrolling by the user and then dynamically determine the updated x, y coördinates, so that we can update the current clickable area.
Content Reference
To get reference to the content component from a pages logic, we can use angular’s @ViewChild annotation and create a variable of type Content and subscribe to its scrolling event. And then pass the x(left offset) and y(top offset) values of the screen to UpdateXY() method, so that it can determine the current position of the Native Ad Unit Creative on the mobile screen.
src/pages/home/home.ts
import { Component, ViewChild } from '@angular/core';
import { NavController, Content } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
@ViewChild(Content) content: Content;
constructor(public navCtrl: NavController, public platform: Platform) {
platform.ready().then(() => {
this.content.ionScroll.subscribe((data) => {
this.updateXY(data.scrollLeft, data.scrollTop);
});
});
};
updateXY(left, top){
var d;
var h, w, x, y;
d = document.getElementById('native');
w = document.getElementById('native').clientWidth;
h = document.getElementById('native').clientHeight;
x = d.offsetLeft - left;
y = d.offsetTop - top;
if(FacebookAds)
FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h);
};
ionViewDidEnter(){
this.platform.ready().then(() => {
if(FacebookAds)
{
FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){
document.addEventListener("onAdLoaded", function(data){
let temp: any = data;
if(temp.adType == "native")
{
document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url));
document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url));
document.getElementById('adTitle').innerHTML = temp.adRes.title;
document.getElementById('adBody').innerHTML = temp.adRes.body;
document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext;
document.getElementById('adBtn').innerHTML = temp.adRes.buttonText;
}
});
}, function(err) { alert(JSON.stringify(err)); });
}
});
};
} |
import { Component, ViewChild } from '@angular/core';
import { NavController, Content } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({ selector: 'page-home', templateUrl: 'home.html'
})
export class HomePage { @ViewChild(Content) content: Content; constructor(public navCtrl: NavController, public platform: Platform) { platform.ready().then(() => { this.content.ionScroll.subscribe((data) => { this.updateXY(data.scrollLeft, data.scrollTop); }); }); }; updateXY(left, top){ var d; var h, w, x, y; d = document.getElementById('native'); w = document.getElementById('native').clientWidth; h = document.getElementById('native').clientHeight; x = d.offsetLeft - left; y = d.offsetTop - top; if(FacebookAds) FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h); }; ionViewDidEnter(){ this.platform.ready().then(() => { if(FacebookAds) { FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){ document.addEventListener("onAdLoaded", function(data){ let temp: any = data; if(temp.adType == "native") { document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url)); document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url)); document.getElementById('adTitle').innerHTML = temp.adRes.title; document.getElementById('adBody').innerHTML = temp.adRes.body; document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext; document.getElementById('adBtn').innerHTML = temp.adRes.buttonText; } }); }, function(err) { alert(JSON.stringify(err)); }); } }); };
}
Look at the code present inside the constructor.
Content Scrolling data
this.content.ionScroll.subscribe((data) => {
this.updateXY(data.scrollLeft, data.scrollTop);
}); |
this.content.ionScroll.subscribe((data) => { this.updateXY(data.scrollLeft, data.scrollTop); });
we only pass scrollLeft and scrollTop values to updateXY() method. The full data emitted is shown below for your reference.
{"timeStamp":17926.585000000003,
"scrollTop":9,
"scrollLeft":0,
"scrollHeight":660,
"scrollWidth":412,
"contentHeight":604,
"contentWidth":412,
"contentTop":56,
"contentBottom":0,
"startY":21,
"startX":0,
"deltaY":-12,
"deltaX":0,
"velocityY":1.195171507111251,
"velocityX":0,
"directionY":"up",
"directionX":"right",
"fixedElement":{},
"scrollElement":{},
"contentElement":{},
"headerElement":{}} |
{"timeStamp":17926.585000000003, "scrollTop":9, "scrollLeft":0, "scrollHeight":660, "scrollWidth":412, "contentHeight":604, "contentWidth":412, "contentTop":56, "contentBottom":0, "startY":21, "startX":0, "deltaY":-12, "deltaX":0, "velocityY":1.195171507111251, "velocityX":0, "directionY":"up", "directionX":"right", "fixedElement":{}, "scrollElement":{}, "contentElement":{}, "headerElement":{}}
With this code we start getting a nice Native Ad creative, but with a clickable area at the top left corner of the screen. That is because updateXY() method is called only when the user scrolls the screen. So we need to call updateXY() once the view is loaded. Lets pass (0, 0) as (x, y) value to updateXY() as there is no changes in the x, y coördinates initially when the app is loaded. We call updateXY() method inside ionViewDidEnter() method, once the Native Ad emits some data.
src/pages/home/home.ts
import { Component, ViewChild } from '@angular/core';
import { NavController, Content } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
@ViewChild(Content) content: Content;
constructor(public navCtrl: NavController, public platform: Platform) {
platform.ready().then(() => {
this.content.ionScroll.subscribe((data) => {
this.updateXY(data.scrollLeft, data.scrollTop);
});
});
};
updateXY(left, top){
var d;
var h, w, x, y;
d = document.getElementById('native');
w = document.getElementById('native').clientWidth;
h = document.getElementById('native').clientHeight;
x = d.offsetLeft - left;
y = d.offsetTop - top;
if(FacebookAds)
FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h);
};
ionViewDidEnter(){
var that = this;
this.platform.ready().then(() => {
if(FacebookAds)
{
FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){
document.addEventListener("onAdLoaded", function(data){
let temp: any = data;
if(temp.adType == "native")
{
document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url));
document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url));
document.getElementById('adTitle').innerHTML = temp.adRes.title;
document.getElementById('adBody').innerHTML = temp.adRes.body;
document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext;
document.getElementById('adBtn').innerHTML = temp.adRes.buttonText;
that.updateXY(0, 0);
}
});
}, function(err) { alert(JSON.stringify(err)); });
}
});
};
} |
import { Component, ViewChild } from '@angular/core';
import { NavController, Content } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({ selector: 'page-home', templateUrl: 'home.html'
})
export class HomePage { @ViewChild(Content) content: Content; constructor(public navCtrl: NavController, public platform: Platform) { platform.ready().then(() => { this.content.ionScroll.subscribe((data) => { this.updateXY(data.scrollLeft, data.scrollTop); }); }); }; updateXY(left, top){ var d; var h, w, x, y; d = document.getElementById('native'); w = document.getElementById('native').clientWidth; h = document.getElementById('native').clientHeight; x = d.offsetLeft - left; y = d.offsetTop - top; if(FacebookAds) FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h); }; ionViewDidEnter(){ var that = this; this.platform.ready().then(() => { if(FacebookAds) { FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){ document.addEventListener("onAdLoaded", function(data){ let temp: any = data; if(temp.adType == "native") { document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url)); document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url)); document.getElementById('adTitle').innerHTML = temp.adRes.title; document.getElementById('adBody').innerHTML = temp.adRes.body; document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext; document.getElementById('adBtn').innerHTML = temp.adRes.buttonText; that.updateXY(0, 0); } }); }, function(err) { alert(JSON.stringify(err)); }); } }); };
}
Here we invoke updateXY(0, 0) initially once the view is loaded i.e., before the user actually scrolls the view area. This removes the top left green clickable area patch. But then there is misplaced clickable area. Looks like the area misplaced is equal to the height of the header element.
Note: Am using that.updateXY() to invoke member function instead of this.updateXY(). To know why I replaced the keyword this in order to invoke member function: setTimeout and setInterval methods: Ionic 2
src/pages/home/home.ts
import { Component, ViewChild } from '@angular/core';
import { NavController, Content } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({
selector: 'page-home',
templateUrl: 'home.html'
})
export class HomePage {
@ViewChild(Content) content: Content;
constructor(public navCtrl: NavController, public platform: Platform) {
platform.ready().then(() => {
this.content.ionScroll.subscribe((data) => {
this.updateXY(data.scrollLeft, data.scrollTop);
});
});
};
updateXY(left, top){
var d;
var h, w, x, y;
d = document.getElementById('native');
var headHeight = document.getElementById("headerHeight").clientHeight;
w = document.getElementById('native').clientWidth;
h = document.getElementById('native').clientHeight;
x = d.offsetLeft - left;
y = d.offsetTop - (top - headHeight);
if(FacebookAds)
FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h);
};
ionViewDidEnter(){
var that = this;
this.platform.ready().then(() => {
if(FacebookAds)
{
FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){
document.addEventListener("onAdLoaded", function(data){
let temp: any = data;
if(temp.adType == "native")
{
document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url));
document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url));
document.getElementById('adTitle').innerHTML = temp.adRes.title;
document.getElementById('adBody').innerHTML = temp.adRes.body;
document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext;
document.getElementById('adBtn').innerHTML = temp.adRes.buttonText;
that.updateXY(0, 0);
}
});
}, function(err) { alert(JSON.stringify(err)); });
}
});
};
} |
import { Component, ViewChild } from '@angular/core';
import { NavController, Content } from 'ionic-angular';
import { Platform } from 'ionic-angular';
declare var FacebookAds: any;
@Component({ selector: 'page-home', templateUrl: 'home.html'
})
export class HomePage { @ViewChild(Content) content: Content; constructor(public navCtrl: NavController, public platform: Platform) { platform.ready().then(() => { this.content.ionScroll.subscribe((data) => { this.updateXY(data.scrollLeft, data.scrollTop); }); }); }; updateXY(left, top){ var d; var h, w, x, y; d = document.getElementById('native'); var headHeight = document.getElementById("headerHeight").clientHeight; w = document.getElementById('native').clientWidth; h = document.getElementById('native').clientHeight; x = d.offsetLeft - left; y = d.offsetTop - (top - headHeight); if(FacebookAds) FacebookAds.setNativeAdClickArea('763416880384578_1139417452784517', x, y, w, h); }; ionViewDidEnter(){ var that = this; this.platform.ready().then(() => { if(FacebookAds) { FacebookAds.createNativeAd('763416880384578_1139417452784517', function(data){ document.addEventListener("onAdLoaded", function(data){ let temp: any = data; if(temp.adType == "native") { document.getElementById('adIcon').setAttribute("src", decodeURIComponent(temp.adRes.icon.url)); document.getElementById('adCover').setAttribute("src", decodeURIComponent(temp.adRes.coverImage.url)); document.getElementById('adTitle').innerHTML = temp.adRes.title; document.getElementById('adBody').innerHTML = temp.adRes.body; document.getElementById('adSocialContext').innerHTML = temp.adRes.socialContext; document.getElementById('adBtn').innerHTML = temp.adRes.buttonText; that.updateXY(0, 0); } }); }, function(err) { alert(JSON.stringify(err)); }); } }); };
}
Go to home.html and assign id of headerHeight to the ion-header tag. Now get its clientHeight and subtract it from the current scrollTop of the screen – inside updateXY() method.
On Device Orientation Change
The top left corner green clickable area spot appears once again when the device orientation is changed. To deal with that lets add a listener for orientation change and then call updateXY() method.
src/pages/home/home.ts
window.addEventListener('orientationchange', function(data){
if(data.isTrusted == 1)
{
this.updateXY(0, 0);
// You can also get the values of x, y, w, h using javascript and then
// assign it to the clickablearea method of FacebookAds.
}
}); |
window.addEventListener('orientationchange', function(data){ if(data.isTrusted == 1) { this.updateXY(0, 0); // You can also get the values of x, y, w, h using javascript and then // assign it to the clickablearea method of FacebookAds. } });
Remove Native Ad
Important: Remove the Native Ad(along with the clickable area) once the user navigates away from the current(Native Ad) view.
src/pages/home/home.ts
ionViewDidLeave(){
if(FacebookAds)
FacebookAds.removeNativeAd('763416880384578_1139417452784517');
}; |
ionViewDidLeave(){ if(FacebookAds) FacebookAds.removeNativeAd('763416880384578_1139417452784517'); };
Make sure to call removeNativeAd() method inside ionViewDidLeave() method in order to remove the Native Ad. Other wise, the clickable area still persists and when user clicks on any other app elements the ad gets triggered and this is against Facebooks(or any other adnetworks) ad policy. So in order to play safe and avoid policy violation or termination of your Facebook Audience Network account, make sure you remove the native ad once the user navigates away from that view.
Important Note
Make sure you pass the same native ad units ID to all 3 methods:
var adId = ‘763416880384578_1139417452784517’;
FacebookAds.createNativeAd(adId);
FacebookAds.setNativeAdClickArea(adId, x, y, w, h);
FacebookAds.removeNativeAd(adId);
Still Clickable area is misplaced or not appearing?
Sometimes I observed that the clickable area doesn’t work properly in test mode. But the same code works perfectly while displaying real ads. So if you have set isTesting to true, then make it false.
How to get Test Ads?
You can simply put this code inside your constructor(wrap it around by platform ready method)
FacebookAds.createBanner({adId: '', isTesting: true}); |
FacebookAds.createBanner({adId: '', isTesting: true});
This show start showing test ads, as FacebookAds variable is a global variable to that component. Make sure to change the value of isTesting to false while moving to production or you can simply remove the code if you’re not showing Facebook audience networks banner ads in your application.
Why is my fill rate less than 100% ?
Fill rate refers the number of ads Facebook return compared to the number of ads you request. There are several reasons why not all requests are filled:
- The person using your app hasn’t logged into the Facebook app in the last 30 days, or the person on your mobile website isn’t logged into Facebook in the same browser. In order to show targeted ads, Audience Network needs to be able to match each person to his or her Facebook profile. Without this match, it can’t fill the request.
- The person using your app or site has turned off targeted advertising in his or her device settings.
- Your app or site has requested too many ads in a short period of time. Audience Network limits ad delivery to a maximum of 25 ads served per 30 minutes to a single person. Facebook also limit ad delivery if your app or site requests more than one ad for the same banner placement within 15 seconds.
- Facebook couldn’t find a relevant ad that matches the targeting and interests of the person using your app or site. Check your filters to make sure you aren’t excluding too many possible advertisers.
Fallback Ad Network
In order to make sure you get near to 100% fill of your ad spot, we need to have a fallback ad network. If Facebooks Audience Network doesn’t have ads to serve to a particular user, then we can invoke some other(alternative/fallback) ad networks Native Ad and fill the ad spot. I’ll be showing it in upcoming video tutorials. Stay subscribed to our blog and our YouTube Channel.
Example Application
We have implemented Facebook Audience Network in one of our applications: RSS Reader
You can use this app to subscribe and keep track of the content you love. You can also subscribe to our blog using the app.