├── .gitignore ├── .jshintrc ├── CHANGELOG.md ├── CONTRIBUTION.md ├── Gruntfile.js ├── LICENSE.md ├── README.md ├── dist ├── appnexus-html5-lib.js └── appnexus-html5-lib.min.js ├── docs ├── Google Web Designer │ └── Standard │ │ ├── an_logo.png │ │ └── index.html ├── Walkthrough-For-Adobe-Edge-Created-Ads.md ├── Walkthrough-For-Google-Web-Designer-Created-Ads.md └── Walkthrough-For-Manually-Created-Ads.md ├── examples ├── ads.json ├── expanding-push │ ├── css │ │ └── main.css │ ├── images │ │ └── an_logo.png │ └── index.html ├── expanding │ ├── css │ │ └── main.css │ ├── images │ │ └── an_logo.png │ └── index.html ├── interstitial │ ├── images │ │ └── an_logo.png │ ├── index.html │ ├── js │ │ └── script.js │ ├── media │ │ └── video.mp4 │ └── styles │ │ └── style.css └── standard │ ├── css │ └── main.css │ ├── images │ └── an_logo.png │ └── index.html ├── package.json ├── src ├── client.js └── lib │ ├── event-listener.js │ ├── guid.js │ ├── porthole.js │ └── utils.js └── test ├── client-ready-test.js ├── client-test.js ├── helpers ├── fixtures.js └── jsdom.js ├── lib-event-listener-test.js ├── lib-guid-test.js ├── lib-porthole-test.js └── lib-utils-test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "browser": true, 3 | "node": true, 4 | "es5": true, 5 | "esnext": true, 6 | "bitwise": false, 7 | "curly": false, 8 | "eqeqeq": true, 9 | "eqnull": true, 10 | "immed": true, 11 | "latedef": false, 12 | "laxcomma": true, 13 | "newcap": true, 14 | "noarg": true, 15 | "undef": true, 16 | "strict": true, 17 | "trailing": true, 18 | "smarttabs": true, 19 | "white": true 20 | } 21 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | == HEAD 2 | -------------------------------------------------------------------------------- /CONTRIBUTION.md: -------------------------------------------------------------------------------- 1 | # Contribution Guidelines 2 | 3 | ## Commit Messages (for Internal AppNexus Contributors) 4 | 5 | Prepend your commit message with the approrpiate JIRA ticket number: `CR-####: ...`. 6 | 7 | ## Merging Branches 8 | 9 | Squashing commits is not mandatory, but can optionally be done to clean up Git history if your change is particularly large. 10 | 11 | ## Raising Github Issues 12 | 13 | If you are an external (non-AppNexus employee) contributor, feel free to create Github issues, and the internal team will respond as soon as possible. 14 | 15 | # Development Guidelines 16 | 17 | ## Installation 18 | 19 | Run the following from the directory where you want the repo to live in 20 | 21 | ``` 22 | git clone html5-lib 23 | cd html5-lib 24 | npm install 25 | ``` 26 | 27 |

28 | ## Development 29 | 30 | You can develop and test on the fly by running: 31 | 32 | ``` 33 | npm run watch 34 | ``` 35 | This will automatically rebuild `appnexus-html5-lib.js` when any of the files under `src` change. 36 | 37 | 38 | To view example creatives, simply double click on them to open them up in your browser. 39 | 40 |

41 | ## Building 42 | 43 | To build the minified version run: 44 | 45 | ``` 46 | npm run build 47 | ``` 48 | 49 | This will build two files, `appnexus-html5-lib.js` and `appnexus-html5-lib.min.js`. 50 | 51 |

52 | ## Deploying 53 | 54 | For deploying run: 55 | 56 | ``` 57 | npm version [major|minor|patch] 58 | ``` 59 | 60 | This will build the library, test it, and create a tag with the new version if tests don't fail. 61 | 62 | 63 | The appnexus-html-lib.min.js needs to be placed on the CDN in a folder corresponding to its version number. for example `https://acdn.adnxs.com/html5-lib/1.0.0/appnexus-html5-lib.min.js`. Always use the version tag file under dist. 64 | 65 | Dont forget to update the examples to point to the latest tag been released. 66 | 67 |

68 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = function (grunt) { 4 | 5 | // Project dependency tasks 6 | grunt.loadNpmTasks('grunt-browserify'); 7 | grunt.loadNpmTasks('grunt-contrib-clean'); 8 | //grunt.loadNpmTasks('grunt-contrib-concat'); 9 | grunt.loadNpmTasks('grunt-contrib-uglify'); 10 | //grunt.loadNpmTasks('grunt-contrib-qunit'); 11 | //grunt.loadNpmTasks('grunt-contrib-jshint'); 12 | grunt.loadNpmTasks('grunt-contrib-watch'); 13 | 14 | // Project configuration 15 | grunt.initConfig({ 16 | pkg: grunt.file.readJSON('package.json'), 17 | clean: { 18 | dist: ['dist/*'], 19 | }, 20 | browserify: { 21 | client: { 22 | browserifyOptions: { 23 | debug: true, 24 | noParse: true 25 | }, 26 | src: ['src/client.js'], 27 | dest: 'dist/<%= pkg.name %>.js' 28 | } 29 | }, 30 | uglify: { 31 | client: { 32 | options: { 33 | banner: '/*\n * <%= pkg.description %> for Client\n * Author: <%= pkg.author.name %> (<%= pkg.author.email %>) \n * Website: <%= pkg.author.url %>\n * <%= pkg.license %> Licensed.\n *\n * <%= pkg.name %>.min.js <%= pkg.version %>\n */\n ' 34 | }, 35 | src: 'dist/<%= pkg.name %>.js', 36 | dest: 'dist/<%= pkg.name %>.min.js' 37 | } 38 | }, 39 | watch: { 40 | test: { 41 | files: 'src/**/*.js', 42 | tasks: ['browserify'] 43 | } 44 | } 45 | }); 46 | 47 | //grunt.registerTask('default', ['jshint', 'qunit', 'clean', 'concat', 'uglify']); 48 | grunt.registerTask('default', ['clean', 'browserify', 'watch']); 49 | grunt.registerTask('build', ['clean', 'browserify', 'uglify']); 50 | }; -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2016 AppNexus Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AppNexus HTML5 Client Library 2 | 3 | The AppNexus HTML5 client library helps integrate HTML5 ads into websites in a safe and secure manner. 4 | 5 | ## API Documentation 6 | 7 | ### `APPNEXUS` Object 8 | 9 | The `APPNEXUS` object is the base object of the API which provides actions to pass down to the publisher website. 10 | 11 | 12 |

13 | ### Method `APPNEXUS.ready(callback) : void` 14 | 15 | The `APPNEXUS.ready()` will trigger `callback` function once the APPNEXUS object has been initialized and the page has been loaded. 16 | 17 | ``` js 18 | APPNEXUS.ready(function () { 19 | var readMoreButton = document.getElementById('read-more-button'); 20 | 21 | readMoreButton.addEventListener("click", function () { 22 | APPNEXUS.click(); 23 | }); 24 | }); 25 | ``` 26 | 27 | #### Multiple `APPNEXUS.ready()` calls 28 | 29 | The library also supports multiple `APPNEXUS.ready()` calls per page. You might want to do this if you have multiple functions that want to check if the APPNEXUS object is initialized and the page is loaded. 30 | 31 | `interaction.js` 32 | 33 | ``` js 34 | APPNEXUS.ready(function () { 35 | var readMoreButton = document.getElementById('read-more-button'); 36 | 37 | readMoreButton.addEventListener("click", function () { 38 | APPNEXUS.click(); 39 | }); 40 | }); 41 | ``` 42 | 43 | `layout.js` 44 | 45 | ``` js 46 | APPNEXUS.ready(function () { 47 | var fullscreenButton = document.getElementById('fullscreen-button'); 48 | 49 | APPNEXUS.setExpandProperties({ 50 | width: 600, 51 | height: 500, 52 | floating: true, 53 | expand: { 54 | easing: 'ease-in-out', 55 | duration: 1000 56 | } 57 | }); 58 | 59 | // Expands on click 60 | fullscreenButton.addEventListener("hover", function () { 61 | APPNEXUS.expand(); 62 | }); 63 | }); 64 | ``` 65 | 66 | 67 |

68 | ### Method `APPNEXUS.click([url]) : void` 69 | 70 | Opens a new window linking to the clickthrough URL or to the specified URL if the `url` parameter is specified. 71 | 72 | 73 | *NOTE: Click-tracking is currently not available when a URL is specified with the `url` parameter.* 74 | 75 | ``` js 76 | APPNEXUS.ready(function () { 77 | var readMoreButton = document.getElementById('read-more-button'); 78 | var facebookButton = document.getElementById('facebook-button'); 79 | 80 | readMoreButton.addEventListener("click", function () { 81 | APPNEXUS.click(); 82 | }); 83 | 84 | facebookButton.addEventListener("click", function () { 85 | // Does not use click-tracking 86 | APPNEXUS.click('http://www.facebook/myawesomeprofile'); 87 | }); 88 | }); 89 | ``` 90 | 91 |

92 | ### Method `APPNEXUS.setExpandProperties(properties) : void` 93 | 94 | Sets the expanding properties of an ad, whether that's an interstitial, a push over, or a floating ad. 95 | 96 | Options for `properties` settings: 97 | 98 | | Property | Type | Value | Default | 99 | |----------------|---------|-------|---------| 100 | | `width` | Number | The expanded `width` in pixels | Current ad "width" | 101 | | `height` | Number | The expanded `height` in pixles | Current ad "height" | 102 | | `floating` | Boolean | Makes the ad float or push content | `false` | 103 | | `anchor` | String | Can be one of the following: `"top-right"`, `"bottom-right"`, `"bottom-left"`, or `"top-left"`
*NOTE: Only works when the `floating` flag is set to true.* | `"top-left"` 104 | | `expand` | Object | Expanding easing animation of the frame. See `easing-properties` option settings. | | 105 | | `collapse` | Object | Collapsing easing animation of the frame. See `easing-properties` option settings. | Inherits `expand` property | 106 | | `interstital` | Boolean | Sets the ad as a full screen interstitial with a light box overlay | `false` | 107 | | `overlayColor` | String | The CSS color of the light box overlay | `"rgba(0,0,0,0.5)"` | 108 | 109 | *NOTE: Setting `interstitial` to `true` will ignore the `floating` value if set together* 110 | 111 |

112 | Options for `easing-properties` settings: 113 | 114 | | Property | Type | Value | Default | 115 | |----------------|---------|-------|---------| 116 | | `easing` | String | CSS [transition-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function). | No easing by default | 117 | | `duration` | Number | CSS transtion duration for `easing` | `400` | 118 | 119 |

120 | Some examples for `APPNEXUS.setExpandingProperties()`: 121 | 122 | **Interstitial Ad Example** 123 | 124 | ``` js 125 | // Ad must call `APPNEXUS.expand()` inside the `APPNEXUS.ready` 126 | APPNEXUS.ready(function () { 127 | ... 128 | APPNEXUS.setExpandProperties({ 129 | interstitial : true 130 | }); 131 | APPNEXUS.expand(); 132 | }); 133 | ``` 134 | 135 | **Expanding (Push Over) Ad Example** 136 | 137 | ``` js 138 | // Original ad size 720x90 that expands to 720x275 139 | APPNEXUS.ready(function () { 140 | var button = document.getElementById('button'); 141 | 142 | APPNEXUS.setExpandProperties({ 143 | height: 275, 144 | expand: { 145 | easing: 'ease-in-out', 146 | duration: 1000 147 | }, 148 | collapse: { 149 | easing: 'ease-in-out', 150 | duration: 500 151 | } 152 | }); 153 | 154 | // Expands on click 155 | button.addEventListener("click", function () { 156 | APPNEXUS.expand(); 157 | }); 158 | }); 159 | ``` 160 | 161 | **Expanding (Floating) Ad Example** 162 | 163 | ``` js 164 | // Original ad size 300x250 that expands to 600x500 165 | APPNEXUS.ready(function () { 166 | var button = document.getElementById('button'); 167 | 168 | APPNEXUS.setExpandProperties({ 169 | width: 600, 170 | height: 500, 171 | floating: true, 172 | expand: { 173 | easing: 'ease-in-out', 174 | duration: 1000 175 | } 176 | }); 177 | 178 | // Expands on click 179 | button.addEventListener("click", function () { 180 | APPNEXUS.expand(); 181 | }); 182 | }); 183 | ``` 184 | 185 |

186 | ### Method `APPNEXUS.getExpandProperties() : Object` 187 | 188 | Returns the current set expanding properties. 189 | 190 |

191 | ### Method `APPNEXUS.expand() : void` 192 | 193 | Triggers the ad to expand to the size specified by the expanding properties in `APPNEXUS.setExpandProperties()`. 194 | 195 |

196 | ### Method `APPNEXUS.collapse() : void` 197 | 198 | Triggers the ad to collapse to the original size. 199 | 200 | 201 |

202 | ### Method `APPNEXUS.getClickTag() : string` 203 | 204 | returns the current clickTag url passed to the creative. This is useful for integration with other ad builders such as adobe edge. 205 | 206 | This function can be called before `APPNEXUS.ready` has fired. 207 | 208 | **Example usage** 209 | 210 | ``` 211 | var clickTag = APPNEXUS.getClickTag(); 212 | ``` 213 | 214 |

215 | ### Method `APPNEXUS.getMacroByName(string) : string` 216 | 217 | returns the value of a given macro passed to the creative. This is useful for GDPR purposes. 218 | 219 | *NOTE: Only works the two `GDPR` macros.* 220 | 221 | **Example usage** 222 | 223 | ```javascript 224 | APPNEXUS.ready(function () { 225 | clickthrough.addEventListener("click", function () { 226 | APPNEXUS.getMacroByName("GDPR_APPLIES"); 227 | APPNEXUS.getMacroByName("GDPR_CONSENT_STRING"); 228 | }); 229 | }); 230 | ``` 231 | 232 |

233 | ## Usage Documentation 234 | 235 | Visit the links below for walkthroughs on how to use the AppNexus HTML5 library in a few specific cases: 236 | 237 | - [Integrating the AppNexus HTML5 Library with Manually Created Creatives](https://github.com/appnexus/appnexus-html5-lib/blob/master/docs/Walkthrough-For-Manually-Created-Ads.md) 238 | - [Integrating the AppNexus HTML5 Library with Ads Created in Google Web Designer](https://wiki.appnexus.com/display/industry/Integrating+the+AppNexus+HTML5+Library+with+Ads+Created+in+Google+Web+Designer) 239 | - [Integrating the AppNexus HTML5 Library with Ads Created in Adobe Edge](https://wiki.appnexus.com/display/industry/Integrating+the+AppNexus+HTML5+Library+with+Ads+Created+in+Adobe+Edge) 240 | 241 | ## Development 242 | 243 | For instructions on how to develop this library, see the [Contribution Guidelines](https://github.com/appnexus/appnexus-html5-lib/blob/master/CONTRIBUTION.md). -------------------------------------------------------------------------------- /dist/appnexus-html5-lib.js: -------------------------------------------------------------------------------- 1 | (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0; i--) { 152 | if (this.__listeners__[i].name === name && this.__listeners__[i].callback === callback) { 153 | this.__listeners__.splice(i, 1); 154 | } 155 | } 156 | } 157 | 158 | EventListener.prototype.dispatchEvent = function (name) { 159 | for (var i = this.__listeners__.length - 1; i >= 0; i--) { 160 | if (this.__listeners__[(this.__listeners__.length - i - 1)].name === name) { 161 | this.__listeners__[(this.__listeners__.length - i - 1)].callback(); 162 | } 163 | } 164 | } 165 | 166 | module.exports = EventListener; 167 | },{}],3:[function(require,module,exports){ 168 | /* 169 | Copyright (c) 2011-2012 Ternary Labs. All Rights Reserved. 170 | 171 | Permission is hereby granted, free of charge, to any person obtaining a copy 172 | of this software and associated documentation files (the "Software"), to deal 173 | in the Software without restriction, including without limitation the rights 174 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 175 | copies of the Software, and to permit persons to whom the Software is 176 | furnished to do so, subject to the following conditions: 177 | 178 | The above copyright notice and this permission notice shall be included in 179 | all copies or substantial portions of the Software. 180 | 181 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 182 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 183 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 184 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 185 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 186 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 187 | THE SOFTWARE. 188 | */ 189 | 190 | /* Simple JavaScript Inheritance 191 | * By John Resig http://ejohn.org/ 192 | * MIT Licensed. 193 | */ 194 | // Inspired by base2 and Prototype 195 | var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; 196 | 197 | // The base Class implementation (does nothing) 198 | var PortholeClass = function(){}; 199 | 200 | // Create a new Class that inherits from this class 201 | PortholeClass.extend = function(prop) { 202 | var _super = this.prototype; 203 | 204 | // Instantiate a base class (but only create the instance, 205 | // don't run the init constructor) 206 | initializing = true; 207 | var prototype = new this(); 208 | initializing = false; 209 | 210 | // Copy the properties over onto the new prototype 211 | for (var name in prop) { 212 | // Check if we're overwriting an existing function 213 | prototype[name] = typeof prop[name] == "function" && 214 | typeof _super[name] == "function" && fnTest.test(prop[name]) ? 215 | (function(name, fn){ 216 | return function() { 217 | var tmp = this._super; 218 | 219 | // Add a new ._super() method that is the same method 220 | // but on the super-class 221 | this._super = _super[name]; 222 | 223 | // The method only need to be bound temporarily, so we 224 | // remove it when we're done executing 225 | var ret = fn.apply(this, arguments); 226 | this._super = tmp; 227 | 228 | return ret; 229 | }; 230 | })(name, prop[name]) : 231 | prop[name]; 232 | } 233 | 234 | // The dummy class constructor 235 | function Class() { 236 | // All construction is actually done in the init method 237 | if ( !initializing && this.init ) 238 | this.init.apply(this, arguments); 239 | } 240 | 241 | // Populate our constructed prototype object 242 | Class.prototype = prototype; 243 | 244 | // Enforce the constructor to be what we expect 245 | Class.prototype.constructor = Class; 246 | 247 | // And make this class extendable 248 | Class.extend = arguments.callee; 249 | 250 | return Class; 251 | }; 252 | 253 | /** 254 | * @overview Porthole, JavaScript Library for Secure Cross Domain iFrame Communication. 255 | * @author Georges Auberger 256 | * @copyright 2011-2012 Ternary Labs, All Rights Reserved. 257 | * 258 | * Namespace for Porthole 259 | * @module Porthole 260 | */ 261 | var Porthole = { 262 | debug: false, 263 | 264 | /** 265 | * Utility function to trace to console 266 | * @private 267 | */ 268 | trace: function(s) { 269 | if (this.debug && window.console !== undefined) { 270 | window.console.log('Porthole: ' + s); 271 | } 272 | }, 273 | 274 | /** 275 | * Utility function to send errors to console 276 | * @private 277 | */ 278 | error: function(s) { 279 | if (typeof window.console !== undefined && typeof window.console.error === 'function') { 280 | window.console.error('Porthole: ' + s); 281 | } 282 | } 283 | }; 284 | 285 | /** 286 | * @class 287 | * @classdesc Proxy window object to post message to target window 288 | * @param {string} proxyIFrameUrl - Fully qualified url to proxy iframe, or null to create a receiver only window 289 | * @param {string} targetWindowName - Name of the proxy iframe window 290 | */ 291 | Porthole.WindowProxy = function(){}; 292 | 293 | Porthole.WindowProxy.prototype = { 294 | /** 295 | * Post a message to the target window only if the content comes from the target origin. 296 | * targetOrigin can be a url or * 297 | * @public 298 | * @param {Object} data - Payload 299 | * @param {String} targetOrigin 300 | */ 301 | post: function(data, targetOrigin) {}, 302 | /** 303 | * Add an event listener to receive messages. 304 | * @public 305 | * @param {Function} eventListenerCallback 306 | * @returns {Function} eventListenerCallback 307 | */ 308 | addEventListener: function(f) {}, 309 | /** 310 | * Remove an event listener. 311 | * @public 312 | * @param {Function} eventListenerCallback 313 | */ 314 | removeEventListener: function(f) {} 315 | }; 316 | 317 | Porthole.WindowProxyBase = PortholeClass.extend({ 318 | init: function(targetWindowName) { 319 | if (targetWindowName === undefined) { 320 | targetWindowName = ''; 321 | } 322 | this.targetWindowName = targetWindowName; 323 | this.origin = window.location.protocol + '//' + window.location.host; 324 | this.eventListeners = []; 325 | }, 326 | 327 | getTargetWindowName: function() { 328 | return this.targetWindowName; 329 | }, 330 | 331 | getOrigin: function() { 332 | return this.origin; 333 | }, 334 | 335 | /** 336 | * Lookup window object based on target window name 337 | * @private 338 | * @return {string} targetWindow 339 | */ 340 | getTargetWindow: function() { 341 | return Porthole.WindowProxy.getTargetWindow(this.targetWindowName); 342 | }, 343 | 344 | post: function(data, targetOrigin) { 345 | if (targetOrigin === undefined) { 346 | targetOrigin = '*'; 347 | } 348 | this.dispatchMessage({ 349 | 'data' : data, 350 | 'sourceOrigin' : this.getOrigin(), 351 | 'targetOrigin' : targetOrigin, 352 | 'sourceWindowName' : window.name, 353 | 'targetWindowName' : this.getTargetWindowName() 354 | }); 355 | }, 356 | 357 | addEventListener: function(f) { 358 | this.eventListeners.push(f); 359 | return f; 360 | }, 361 | 362 | removeEventListener: function(f) { 363 | var index; 364 | try { 365 | index = this.eventListeners.indexOf(f); 366 | this.eventListeners.splice(index, 1); 367 | } catch(e) { 368 | this.eventListeners = []; 369 | } 370 | }, 371 | 372 | dispatchEvent: function(event) { 373 | var i; 374 | for (i = 0; i < this.eventListeners.length; i++) { 375 | try { 376 | this.eventListeners[i](event); 377 | } catch(e) { 378 | Porthole.error(e); 379 | } 380 | } 381 | } 382 | }); 383 | 384 | /** 385 | * Legacy browser implementation of proxy window object to post message to target window 386 | * 387 | * @private 388 | * @constructor 389 | * @param {string} proxyIFrameUrl - Fully qualified url to proxy iframe 390 | * @param {string} targetWindowName - Name of the proxy iframe window 391 | */ 392 | Porthole.WindowProxyLegacy = Porthole.WindowProxyBase.extend({ 393 | init: function(proxyIFrameUrl, targetWindowName) { 394 | this._super(targetWindowName); 395 | 396 | if (proxyIFrameUrl !== null) { 397 | this.proxyIFrameName = this.targetWindowName + 'ProxyIFrame'; 398 | this.proxyIFrameLocation = proxyIFrameUrl; 399 | 400 | // Create the proxy iFrame and add to dom 401 | this.proxyIFrameElement = this.createIFrameProxy(); 402 | } else { 403 | // Won't be able to send messages 404 | this.proxyIFrameElement = null; 405 | Porthole.trace("proxyIFrameUrl is null, window will be a receiver only"); 406 | this.post = function(){ throw new Error("Receiver only window");}; 407 | } 408 | }, 409 | 410 | /** 411 | * Create an iframe and load the proxy 412 | * 413 | * @private 414 | * @returns iframe 415 | */ 416 | createIFrameProxy: function() { 417 | var iframe = document.createElement('iframe'); 418 | 419 | iframe.setAttribute('id', this.proxyIFrameName); 420 | iframe.setAttribute('name', this.proxyIFrameName); 421 | iframe.setAttribute('src', this.proxyIFrameLocation); 422 | // IE needs this otherwise resize event is not fired 423 | iframe.setAttribute('frameBorder', '1'); 424 | iframe.setAttribute('scrolling', 'auto'); 425 | // Need a certain size otherwise IE7 does not fire resize event 426 | iframe.setAttribute('width', 30); 427 | iframe.setAttribute('height', 30); 428 | iframe.setAttribute('style', 'position: absolute; left: -100px; top:0px;'); 429 | // IE needs this because setting style attribute is broken. No really. 430 | if (iframe.style.setAttribute) { 431 | iframe.style.setAttribute('cssText', 'position: absolute; left: -100px; top:0px;'); 432 | } 433 | document.body.appendChild(iframe); 434 | return iframe; 435 | }, 436 | 437 | dispatchMessage: function(message) { 438 | var encode = window.encodeURIComponent; 439 | 440 | if (this.proxyIFrameElement) { 441 | var src = this.proxyIFrameLocation + '#' + encode(Porthole.WindowProxy.serialize(message)); 442 | this.proxyIFrameElement.setAttribute('src', src); 443 | this.proxyIFrameElement.height = this.proxyIFrameElement.height > 50 ? 50 : 100; 444 | } 445 | } 446 | }); 447 | 448 | /** 449 | * Implementation for modern browsers that supports it 450 | */ 451 | Porthole.WindowProxyHTML5 = Porthole.WindowProxyBase.extend({ 452 | init: function(proxyIFrameUrl, targetWindowName) { 453 | this._super(targetWindowName); 454 | this.eventListenerCallback = null; 455 | }, 456 | 457 | dispatchMessage: function(message) { 458 | this.getTargetWindow().postMessage(Porthole.WindowProxy.serialize(message), message.targetOrigin); 459 | }, 460 | 461 | addEventListener: function(f) { 462 | if (this.eventListeners.length === 0) { 463 | var self = this; 464 | if (window.addEventListener) { 465 | this.eventListenerCallback = function(event) { self.eventListener(self, event); }; 466 | window.addEventListener('message', this.eventListenerCallback, false); 467 | } else if (window.attachEvent) { 468 | // Make IE8 happy, just not that 1. postMessage only works for IFRAMES/FRAMES http://blogs.msdn.com/b/ieinternals/archive/2009/09/16/bugs-in-ie8-support-for-html5-postmessage-sessionstorage-and-localstorage.aspx 469 | this.eventListenerCallback = function(event) { self.eventListener(self, window.event); }; 470 | window.attachEvent("onmessage", this.eventListenerCallback); 471 | } 472 | } 473 | return this._super(f); 474 | }, 475 | 476 | removeEventListener: function(f) { 477 | this._super(f); 478 | 479 | if (this.eventListeners.length === 0) { 480 | if (window.removeEventListener) { 481 | window.removeEventListener('message', this.eventListenerCallback); 482 | } else if (window.detachEvent) { // Make IE8, happy, see above 483 | // see jquery, detachEvent needed property on element, by name of that event, to properly expose it to GC 484 | if (typeof window.onmessage === 'undefined') window.onmessage = null; 485 | window.detachEvent('onmessage', this.eventListenerCallback); 486 | } 487 | this.eventListenerCallback = null; 488 | } 489 | }, 490 | 491 | eventListener: function(self, nativeEvent) { 492 | var data = Porthole.WindowProxy.unserialize(nativeEvent.data); 493 | if (data && (self.targetWindowName === '' || data.sourceWindowName == self.targetWindowName)) { 494 | self.dispatchEvent(new Porthole.MessageEvent(data.data, nativeEvent.origin, self)); 495 | } 496 | } 497 | }); 498 | 499 | if (!window.postMessage) { 500 | Porthole.trace('Using legacy browser support'); 501 | Porthole.WindowProxy = Porthole.WindowProxyLegacy.extend({}); 502 | } else { 503 | Porthole.trace('Using built-in browser support'); 504 | Porthole.WindowProxy = Porthole.WindowProxyHTML5.extend({}); 505 | } 506 | 507 | /** 508 | * Serialize an object using JSON.stringify 509 | * 510 | * @param {Object} obj The object to be serialized 511 | * @return {String} 512 | */ 513 | Porthole.WindowProxy.serialize = function(obj) { 514 | if (typeof JSON === 'undefined') { 515 | throw new Error('Porthole serialization depends on JSON!'); 516 | } 517 | 518 | return JSON.stringify(obj); 519 | }; 520 | 521 | /** 522 | * Unserialize using JSON.parse 523 | * 524 | * @param {String} text Serialization 525 | * @return {Object} 526 | */ 527 | Porthole.WindowProxy.unserialize = function(text) { 528 | if (typeof JSON === 'undefined') { 529 | throw new Error('Porthole unserialization dependens on JSON!'); 530 | } 531 | try { 532 | var json = JSON.parse(text); 533 | } catch (e) { 534 | return false; 535 | } 536 | return json; 537 | }; 538 | 539 | Porthole.WindowProxy.getTargetWindow = function(targetWindowName) { 540 | if (targetWindowName === '') { 541 | return window.parent; 542 | } else if (targetWindowName === 'top' || targetWindowName === 'parent') { 543 | return window[targetWindowName]; 544 | } 545 | return window.frames[targetWindowName]; 546 | }; 547 | 548 | /** 549 | * @classdesc Event object to be passed to registered event handlers 550 | * @class 551 | * @param {String} data 552 | * @param {String} origin - url of window sending the message 553 | * @param {Object} source - window object sending the message 554 | */ 555 | Porthole.MessageEvent = function MessageEvent(data, origin, source) { 556 | this.data = data; 557 | this.origin = origin; 558 | this.source = source; 559 | }; 560 | 561 | /** 562 | * @classdesc Dispatcher object to relay messages. 563 | * @public 564 | * @constructor 565 | */ 566 | Porthole.WindowProxyDispatcher = { 567 | /** 568 | * Forward a message event to the target window 569 | * @private 570 | */ 571 | forwardMessageEvent: function(e) { 572 | var message, 573 | decode = window.decodeURIComponent, 574 | targetWindow, 575 | windowProxy; 576 | 577 | if (document.location.hash.length > 0) { 578 | // Eat the hash character 579 | message = Porthole.WindowProxy.unserialize(decode(document.location.hash.substr(1))); 580 | 581 | targetWindow = Porthole.WindowProxy.getTargetWindow(message.targetWindowName); 582 | 583 | windowProxy = 584 | Porthole.WindowProxyDispatcher.findWindowProxyObjectInWindow( 585 | targetWindow, 586 | message.sourceWindowName 587 | ); 588 | 589 | if (windowProxy) { 590 | if (windowProxy.origin === message.targetOrigin || message.targetOrigin === '*') { 591 | windowProxy.dispatchEvent( 592 | new Porthole.MessageEvent(message.data, message.sourceOrigin, windowProxy)); 593 | } else { 594 | Porthole.error('Target origin ' + 595 | windowProxy.origin + 596 | ' does not match desired target of ' + 597 | message.targetOrigin); 598 | } 599 | } else { 600 | Porthole.error('Could not find window proxy object on the target window'); 601 | } 602 | } 603 | }, 604 | 605 | /** 606 | * Look for a window proxy object in the target window 607 | * @private 608 | */ 609 | findWindowProxyObjectInWindow: function(w, sourceWindowName) { 610 | var i; 611 | 612 | if (w) { 613 | for (i in w) { 614 | if (Object.prototype.hasOwnProperty.call(w, i)) { 615 | try { 616 | // Ensure that we're finding the proxy object 617 | // that is declared to be targetting the window that is calling us 618 | if (w[i] !== null && 619 | typeof w[i] === 'object' && 620 | w[i] instanceof w.Porthole.WindowProxy && 621 | w[i].getTargetWindowName() === sourceWindowName) { 622 | return w[i]; 623 | } 624 | } catch(e) { 625 | // Swallow exception in case we access an object we shouldn't 626 | } 627 | } 628 | } 629 | } 630 | return null; 631 | }, 632 | 633 | /** 634 | * Start a proxy to relay messages. 635 | * @public 636 | */ 637 | start: function() { 638 | if (window.addEventListener) { 639 | window.addEventListener('resize', 640 | Porthole.WindowProxyDispatcher.forwardMessageEvent, 641 | false); 642 | } else if (window.attachEvent && window.postMessage !== 'undefined') { 643 | window.attachEvent('onresize', 644 | Porthole.WindowProxyDispatcher.forwardMessageEvent); 645 | } else if (document.body.attachEvent) { 646 | window.attachEvent('onresize', 647 | Porthole.WindowProxyDispatcher.forwardMessageEvent); 648 | } else { 649 | // Should never happen 650 | Porthole.error('Cannot attach resize event'); 651 | } 652 | } 653 | }; 654 | 655 | module.exports = Porthole; 656 | 657 | },{}]},{},[1]); 658 | -------------------------------------------------------------------------------- /dist/appnexus-html5-lib.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * AppNexus HTML5 Client Library for Client 3 | * Author: AppNexus () 4 | * Website: http://www.appnexus.com 5 | * Apache-2.0 Licensed. 6 | * 7 | * appnexus-html5-lib.min.js 1.4.1 8 | */ 9 | !function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);var j=new Error("Cannot find module '"+g+"'");throw j.code="MODULE_NOT_FOUND",j}var k=c[g]={exports:{}};b[g][0].call(k.exports,function(a){var c=b[g][1][a];return e(c?c:a)},k,k.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;g=0;c--)this.__listeners__[c].name===a&&this.__listeners__[c].callback===b&&this.__listeners__.splice(c,1)},d.prototype.dispatchEvent=function(a){for(var b=this.__listeners__.length-1;b>=0;b--)this.__listeners__[this.__listeners__.length-b-1].name===a&&this.__listeners__[this.__listeners__.length-b-1].callback()},b.exports=d},{}],3:[function(a,b,c){var d=!1,e=/xyz/.test(function(){xyz})?/\b_super\b/:/.*/,f=function(){};f.extend=function(a){function b(){!d&&this.init&&this.init.apply(this,arguments)}var c=this.prototype;d=!0;var f=new this;d=!1;for(var g in a)f[g]="function"==typeof a[g]&&"function"==typeof c[g]&&e.test(a[g])?function(a,b){return function(){var d=this._super;this._super=c[a];var e=b.apply(this,arguments);return this._super=d,e}}(g,a[g]):a[g];return b.prototype=f,b.prototype.constructor=b,b.extend=arguments.callee,b};var g={debug:!1,trace:function(a){this.debug&&void 0!==window.console&&window.console.log("Porthole: "+a)},error:function(a){void 0!==typeof window.console&&"function"==typeof window.console.error&&window.console.error("Porthole: "+a)}};g.WindowProxy=function(){},g.WindowProxy.prototype={post:function(a,b){},addEventListener:function(a){},removeEventListener:function(a){}},g.WindowProxyBase=f.extend({init:function(a){void 0===a&&(a=""),this.targetWindowName=a,this.origin=window.location.protocol+"//"+window.location.host,this.eventListeners=[]},getTargetWindowName:function(){return this.targetWindowName},getOrigin:function(){return this.origin},getTargetWindow:function(){return g.WindowProxy.getTargetWindow(this.targetWindowName)},post:function(a,b){void 0===b&&(b="*"),this.dispatchMessage({data:a,sourceOrigin:this.getOrigin(),targetOrigin:b,sourceWindowName:window.name,targetWindowName:this.getTargetWindowName()})},addEventListener:function(a){return this.eventListeners.push(a),a},removeEventListener:function(a){var b;try{b=this.eventListeners.indexOf(a),this.eventListeners.splice(b,1)}catch(c){this.eventListeners=[]}},dispatchEvent:function(a){var b;for(b=0;b50?50:100}}}),g.WindowProxyHTML5=g.WindowProxyBase.extend({init:function(a,b){this._super(b),this.eventListenerCallback=null},dispatchMessage:function(a){this.getTargetWindow().postMessage(g.WindowProxy.serialize(a),a.targetOrigin)},addEventListener:function(a){if(0===this.eventListeners.length){var b=this;window.addEventListener?(this.eventListenerCallback=function(a){b.eventListener(b,a)},window.addEventListener("message",this.eventListenerCallback,!1)):window.attachEvent&&(this.eventListenerCallback=function(a){b.eventListener(b,window.event)},window.attachEvent("onmessage",this.eventListenerCallback))}return this._super(a)},removeEventListener:function(a){this._super(a),0===this.eventListeners.length&&(window.removeEventListener?window.removeEventListener("message",this.eventListenerCallback):window.detachEvent&&("undefined"==typeof window.onmessage&&(window.onmessage=null),window.detachEvent("onmessage",this.eventListenerCallback)),this.eventListenerCallback=null)},eventListener:function(a,b){var c=g.WindowProxy.unserialize(b.data);!c||""!==a.targetWindowName&&c.sourceWindowName!=a.targetWindowName||a.dispatchEvent(new g.MessageEvent(c.data,b.origin,a))}}),window.postMessage?(g.trace("Using built-in browser support"),g.WindowProxy=g.WindowProxyHTML5.extend({})):(g.trace("Using legacy browser support"),g.WindowProxy=g.WindowProxyLegacy.extend({})),g.WindowProxy.serialize=function(a){if("undefined"==typeof JSON)throw new Error("Porthole serialization depends on JSON!");return JSON.stringify(a)},g.WindowProxy.unserialize=function(a){if("undefined"==typeof JSON)throw new Error("Porthole unserialization dependens on JSON!");try{var b=JSON.parse(a)}catch(c){return!1}return b},g.WindowProxy.getTargetWindow=function(a){return""===a?window.parent:"top"===a||"parent"===a?window[a]:window.frames[a]},g.MessageEvent=function(a,b,c){this.data=a,this.origin=b,this.source=c},g.WindowProxyDispatcher={forwardMessageEvent:function(a){var b,c,d,e=window.decodeURIComponent;document.location.hash.length>0&&(b=g.WindowProxy.unserialize(e(document.location.hash.substr(1))),c=g.WindowProxy.getTargetWindow(b.targetWindowName),d=g.WindowProxyDispatcher.findWindowProxyObjectInWindow(c,b.sourceWindowName),d?d.origin===b.targetOrigin||"*"===b.targetOrigin?d.dispatchEvent(new g.MessageEvent(b.data,b.sourceOrigin,d)):g.error("Target origin "+d.origin+" does not match desired target of "+b.targetOrigin):g.error("Could not find window proxy object on the target window"))},findWindowProxyObjectInWindow:function(a,b){var c;if(a)for(c in a)if(Object.prototype.hasOwnProperty.call(a,c))try{if(null!==a[c]&&"object"==typeof a[c]&&a[c]instanceof a.Porthole.WindowProxy&&a[c].getTargetWindowName()===b)return a[c]}catch(d){}return null},start:function(){window.addEventListener?window.addEventListener("resize",g.WindowProxyDispatcher.forwardMessageEvent,!1):window.attachEvent&&"undefined"!==window.postMessage?window.attachEvent("onresize",g.WindowProxyDispatcher.forwardMessageEvent):document.body.attachEvent?window.attachEvent("onresize",g.WindowProxyDispatcher.forwardMessageEvent):g.error("Cannot attach resize event")}},b.exports=g},{}]},{},[1]); -------------------------------------------------------------------------------- /docs/Google Web Designer/Standard/an_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/652b5e64437c08ece1d84a3b9856b2e1facd93b7/docs/Google Web Designer/Standard/an_logo.png -------------------------------------------------------------------------------- /docs/Google Web Designer/Standard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 26 | 127 | 128 | 129 | 130 | 131 | 132 | 147 | 170 | 184 | 185 | 195 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 |
216 |
217 |
218 | 219 | 220 |

Powering the advertising
221 | that powers the
 internet 222 |

223 |
224 |
225 |
226 |
227 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /docs/Walkthrough-For-Adobe-Edge-Created-Ads.md: -------------------------------------------------------------------------------- 1 | #How To: Integrate the AppNexus HTML5 Library with Ads Created in Adobe Edge 2 | 3 | ##Converting an Existing Adobe Edge Ad 4 | 5 | If you are working with an ad already created in Adobe Edge, you can follow these instructions to modify that ad so it works with the AppNexus HTML5 Library. 6 | 7 | 8 | ##Standard Ads 9 | 10 | ###Step 1: Find the right files to edit 11 | Before we begin, find the folder containing the Adobe Edge-created ad. You may only have a file with a `.zip` extension—in this case, you must unzip the file to reveal a folder containing its various assets. 12 | 13 | Then, look for and open the file named `index.html`. There will also be a file with the extension `.js` at the root level of the ad folder, that looks like `300x250edge.js` (it has also been named `edgeActions.js` in some cases). These files are where we will make all of the necessary changes in the steps below. 14 | 15 | 16 | ###Step 2: Add the AppNexus HTML5 Library 17 | We will have to make sure the actual AppNexus HTML5 Library is linked to inside `index.html`. The library can be found here: [https://acdn.adnxs.com/html5-lib/1.4.1/appnexus-html5-lib.min.js](https://acdn.adnxs.com/html5-lib/1.4.1/appnexus-html5-lib.min.js) 18 | 19 | Linking the library in should be done inside the `` tag in the `index.html` file, by adding the following ` 25 | 26 | ``` 27 | 28 | 29 | 30 | ###Step 3: Add Click Event 31 | For this step, you will have to make all your changes to the `300x250edge.js`/`...edge.js`/`edgeActions.js` file. 32 | 33 | ####Without Existing Click Event 34 | You could see an Adobe Edge JavaScript function that handles events, but has no existing click events. This looks something like this: 35 | 36 | ```javascript 37 | //Edge symbol: 'stage' 38 | (function(symbolName){ 39 | Symbol.bindTriggerAction(compId,symbolName,"Default Timeline",16750,function(sym,e){ 40 | sym.play(0); 41 | }); 42 | //Edge binding end 43 | })("stage"); 44 | ``` 45 | 46 | In order to support the click event using the `APPNEXUS.click()` function, you will have to add in the following function: 47 | 48 | ```javascript 49 | Symbol.bindElementAction(compId,symbolName,"${Stage}","click",function(sym,e){ 50 | APPNEXUS.click(); 51 | }); 52 | ``` 53 | Putting it all together, you will have a function that looks like this: 54 | 55 | ```javascript 56 | //Edge symbol: 'stage' 57 | (function(symbolName){ 58 | Symbol.bindTriggerAction(compId,symbolName,"Default Timeline",16750,function(sym,e){ 59 | sym.play(0); 60 | }); 61 | Symbol.bindElementAction(compId,symbolName,"${Stage}","click",function(sym,e){ 62 | APPNEXUS.click(); 63 | }); 64 | //Edge binding end 65 | })("stage"); 66 | ``` 67 | 68 | ####With Existing Click Event 69 | You could see an Adobe Edge JavaScript function that handles events, and already has an existing click event. This looks something like this: 70 | 71 | ```javascript 72 | //Edge symbol: 'stage' 73 | (function(symbolName){ 74 | Symbol.bindElementAction(compId, symbolName, "${Stage}", "click", function(sym, e) { 75 | window.open("https://appnexus.com"); 76 | }); 77 | //Edge binding end 78 | })("stage"); 79 | ``` 80 | 81 | In order to support the click event using the `APPNEXUS.click()` function, you will have to replace the hard-coded URL, `https://appnexus.com` to add in the function `APPNEXUS.getClickTag()`. 82 | 83 | 84 | Putting it all together, you will have a function that looks like this: 85 | 86 | ```javascript 87 | //Edge symbol: 'stage' 88 | (function(symbolName){ 89 | Symbol.bindElementAction(compId, symbolName, "${Stage}", "click", function(sym, e) { 90 | window.open(APPNEXUS.getClickTag()); 91 | }); 92 | //Edge binding end 93 | })("stage"); 94 | ``` 95 | -------------------------------------------------------------------------------- /docs/Walkthrough-For-Google-Web-Designer-Created-Ads.md: -------------------------------------------------------------------------------- 1 | #How To: Integrate the AppNexus HTML5 Library with Ads Created in Google Web Designer 2 | 3 | ##Converting an Existing Google Web Designer Ad 4 | 5 | If you are working with an ad already created in Google Web Designer, you can follow these instructions to modify that ad so it works with the AppNexus HTML5 Library. 6 | 7 | 8 | ##Standard Ads 9 | The corresponding example for this section can be found in this folder under `/Google Web Designer/Standard/`. 10 | 11 | ###Step 1: Find the `index.html` file 12 | Before we begin, find the folder containing the Google Web Designer-created ad. You may only have a file with a `.zip` extension—in this case, you must unzip the file to reveal a folder containing its various assets. 13 | 14 | 15 | Then, look for and open the file named `index.html`. This file is where we will make all of the necessary changes in the steps below. 16 | 17 | 18 | ###Step 2: Add the AppNexus HTML5 Library 19 | First, we will have to make sure the actual HTML5 Library is linked to inside `index.html`. This should be done right before the `` tag in the `index.html` file, by adding the following ` 25 | 26 | ``` 27 | 28 | 29 | 30 | ###Step 3: Replace hard-coded URL with `APPNEXUS.getClickTag()` 31 | Generally, you may have an ad created in Google Web Designer that has a hard-coded URL embedded. 32 | 33 | In order to remove them, search for `gwd-events="handlers"` in the same `index.html` file. You should find a block of code that looks like this: 34 | 35 | ```html 36 | 42 | ``` 43 | Note that the hardcoded URL (in this case, `https://appnexus.com`) will vary based on the specific ad you are working with. 44 | 45 | Then, replace `"https://appnexus.com"` (including the quotation marks) with `APPNEXUS.getClickTag()`, so that this function looks like: 46 | 47 | ```html 48 | 54 | ``` 55 | 56 | ###Step 4: Finish and Upload 57 | 58 | Then, ZIP the folder containing `index.html` and the rest of your ad's assets and upload it using the [AppNexus Hosted HTML5 Creative Upload](https://wiki.appnexus.com/display/console/Upload+Hosted+HTML5+Creatives). -------------------------------------------------------------------------------- /docs/Walkthrough-For-Manually-Created-Ads.md: -------------------------------------------------------------------------------- 1 | #Intergrating the AppNexus HTML5 Library with Manually Created HTML5 Ads 2 | 3 | ## Table of Contents 4 | - [Standard Ads](#standard-ads) 5 | - [Expanding Ads](#expanding-ads) 6 | - [Interstitial Ads](#interstitial-ads) 7 | 8 | 9 | ## Standard Ads 10 | All standard ads require a file called `index.html`. Each of the following steps makes changes to that file. 11 | 12 | ####Step 1: Add the AppNexus HTML5 Library 13 | 14 | You can find the AppNexus' HTML5 JavaScript library at this URL: [https://acdn.adnxs.com/html5-lib/1.4.1/appnexus-html5-lib.min.js](https://acdn.adnxs.com/html5-lib/1.4.1/appnexus-html5-lib.min.js). 15 | 16 | You must add it to the ad's `index.html` file, inside the `` tag, in a ` 22 | 23 | ``` 24 | 25 | ####Step 2: Add a clickthrough element 26 | Add a unique id `"clickthrough"` to `
` (if that does not exist, you can add it to the `` tag), 27 | 28 | so that this: 29 | 30 | ```html 31 |
32 | ``` 33 | becomes this: 34 | 35 | ```html 36 |
37 | ``` 38 | 39 | ####Step 3: Handle click event 40 | Standard ads will make use of the `APPNEXUS.ready()` and `APPNEXUS.click()` functions (provided by the AppNexus HTML5 JavaScript library you added in Step 1). 41 | 42 | Before the closing body tag (``), you can copy and paste the following ` 54 | 55 | ``` 56 | 57 | __Brief Technical Explanation__ 58 | 59 | When the page is loaded and the HTML5 Library has loaded, `APPNEXUS.ready()` is called. `APPNEXUS.click()` is called inside the `addEventListener` callback function — this means that it happens when the user clicks on the `clickthrough` element. 60 | 61 | See `README.md` for additional technical documentation on `APPNEXUS.ready()` and `APPNEXUS.click()`. 62 | 63 | 64 | ## Expanding Ads 65 | All expanding ads require a file called `index.html`. Each of the following steps makes changes to that file. 66 | 67 | ####Step 1: Add the AppNexus HTML5 Library 68 | You can find the AppNexus' HTML5 JavaScript library at this URL: [https://acdn.adnxs.com/html5-lib/1.4.1/appnexus-html5-lib.min.js](https://acdn.adnxs.com/html5-lib/1.4.1/appnexus-html5-lib.min.js). 69 | 70 | You must add it to the ad's `index.html` file, inside the `` tag, in a ` 76 | 77 | ``` 78 | 79 | ####Step 2: Add a clickthrough element 80 | Add a unique id `"clickthrough"` to `
` (if that does not exist, you can add it to the `` tag), 81 | 82 | so this: 83 | 84 | ```html 85 |
86 | ``` 87 | becomes 88 | 89 | ```html 90 |
91 | ``` 92 | 93 | ####Step 3: Configure expanding properties 94 | To make your expanding ad work properly, you will need to add the following ` 143 | 144 | ``` 145 | 146 | ##### Animating the containing `