├── CHANGELOG.md ├── .gitignore ├── examples ├── expanding │ ├── images │ │ └── an_logo.png │ ├── index.html │ └── css │ │ └── main.css ├── interstitial │ ├── media │ │ └── video.mp4 │ ├── images │ │ └── an_logo.png │ ├── styles │ │ └── style.css │ ├── js │ │ └── script.js │ └── index.html ├── standard │ ├── images │ │ └── an_logo.png │ ├── index.html │ └── css │ │ └── main.css ├── expanding-push │ ├── images │ │ └── an_logo.png │ ├── index.html │ └── css │ │ └── main.css └── ads.json ├── docs ├── Google Web Designer │ └── Standard │ │ ├── an_logo.png │ │ └── index.html ├── Walkthrough-For-Google-Web-Designer-Created-Ads.md ├── Walkthrough-For-Adobe-Edge-Created-Ads.md └── Walkthrough-For-Manually-Created-Ads.md ├── src ├── lib │ ├── guid.js │ ├── utils.js │ ├── event-listener.js │ └── porthole.js └── client.js ├── .jshintrc ├── LICENSE.md ├── test ├── lib-porthole-test.js ├── lib-guid-test.js ├── lib-utils-test.js ├── helpers │ ├── jsdom.js │ └── fixtures.js ├── lib-event-listener-test.js ├── client-ready-test.js └── client-test.js ├── package.json ├── Gruntfile.js ├── CONTRIBUTION.md ├── README.md └── dist ├── appnexus-html5-lib.min.js └── appnexus-html5-lib.js /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | == HEAD 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .idea 4 | npm-debug.log 5 | -------------------------------------------------------------------------------- /examples/expanding/images/an_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/HEAD/examples/expanding/images/an_logo.png -------------------------------------------------------------------------------- /examples/interstitial/media/video.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/HEAD/examples/interstitial/media/video.mp4 -------------------------------------------------------------------------------- /examples/standard/images/an_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/HEAD/examples/standard/images/an_logo.png -------------------------------------------------------------------------------- /examples/interstitial/images/an_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/HEAD/examples/interstitial/images/an_logo.png -------------------------------------------------------------------------------- /examples/expanding-push/images/an_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/HEAD/examples/expanding-push/images/an_logo.png -------------------------------------------------------------------------------- /docs/Google Web Designer/Standard/an_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/appnexus/appnexus-html5-lib/HEAD/docs/Google Web Designer/Standard/an_logo.png -------------------------------------------------------------------------------- /src/lib/guid.js: -------------------------------------------------------------------------------- 1 | module.exports = function guid() { 2 | function s4() { 3 | return Math.floor((1 + Math.random()) * 0x10000) 4 | .toString(16) 5 | .substring(1); 6 | } 7 | return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4(); 8 | } -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/lib-porthole-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jsdom = require('./helpers/jsdom'); 3 | var fixtures = require('./helpers/fixtures') 4 | 5 | describe('Porthole', function () { 6 | before(function (done) { 7 | jsdom.createPage(fixtures.HTML5_ADVERTISEMENT_URL, fixtures.HTML5_WEBPAGE, function (window) { 8 | global.window = window; 9 | done(); 10 | }); 11 | }) 12 | 13 | after(function () { 14 | global.window = undefined; 15 | }) 16 | 17 | it('checks it exits', function () { 18 | var porthole = require('../src/lib/porthole'); 19 | expect(porthole).to.exist; 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/lib-guid-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var guid = require('../src/lib/guid'); 3 | 4 | describe('Generate unique ID', function () { 5 | it('checks it is a function', function () { 6 | expect(guid).to.be.a('function'); 7 | }); 8 | 9 | it('checks it generates a string', function () { 10 | var result = guid(); 11 | expect(result).to.be.a('string'); 12 | }); 13 | 14 | it('checks if it generates different id', function () { 15 | var result1 = guid(); 16 | var result2 = guid(); 17 | var result3 = guid(); 18 | 19 | expect(result1).to.not.equal(result2); 20 | expect(result2).to.not.equal(result3); 21 | expect(result1).to.not.equal(result3); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /test/lib-utils-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var sprintf = require('../src/lib/utils').sprintf; 3 | var deepExtend = require('../src/lib/utils').deepExtend; 4 | 5 | describe('Util functions', function () { 6 | it('checks sprintf works properly', function () { 7 | var result = sprintf("There are %s monkeys in %s.", 13, 'Disney'); 8 | expect(result).to.be.equal("There are 13 monkeys in Disney."); 9 | }); 10 | 11 | it('checks deepExtend works properly', function () { 12 | var result = deepExtend({}, {'age': 3}, {'gender': 'male', 'father': {'age': 30, 'gender': 'male'}}); 13 | expect(result).to.deep.equal({ age: 3, gender: 'male', father: { age: 30, gender: 'male' } }) 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /examples/ads.json: -------------------------------------------------------------------------------- 1 | { 2 | "ads": [ 3 | { 4 | "id": 15, 5 | "type":"standard", 6 | "path": "standard/index.html", 7 | "url": "http://www.appnexus.com", 8 | "size": "300x250" 9 | }, 10 | { 11 | "id": 18, 12 | "type": "expanding-push", 13 | "path": "expanding-push/index.html", 14 | "url": "http://www.appnexus.com", 15 | "size": "728x90" 16 | }, 17 | { 18 | "id": 20, 19 | "type": "expanding", 20 | "path": "expanding/index.html", 21 | "url": "https://www.appnexus.com", 22 | "size": "300x250" 23 | }, 24 | { 25 | "id": 13, 26 | "type": "interstitial", 27 | "path": "interstitial/index.html", 28 | "url": "http://www.appnexus.com", 29 | "size": "0x0" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /src/lib/utils.js: -------------------------------------------------------------------------------- 1 | var utils = { 2 | sprintf: function (format) { 3 | var args = arguments; 4 | var index = 0; 5 | return format.replace(/%s/g, function (dummy, match) { 6 | return args[++index]; 7 | }); 8 | }, 9 | //http://youmightnotneedjquery.com/#deep_extend 10 | deepExtend: function(out) { 11 | out = out || {}; 12 | 13 | for (var i = 1; i < arguments.length; i++) { 14 | var obj = arguments[i]; 15 | if (!obj) continue; 16 | for (var key in obj) { 17 | if (obj.hasOwnProperty(key)) { 18 | if (typeof obj[key] === 'object') 19 | out[key] = utils.deepExtend(out[key], obj[key]); 20 | else 21 | out[key] = obj[key]; 22 | } 23 | } 24 | } 25 | 26 | return out; 27 | } 28 | } 29 | 30 | module.exports = utils; -------------------------------------------------------------------------------- /src/lib/event-listener.js: -------------------------------------------------------------------------------- 1 | function EventListener() { 2 | this.__listeners__ = []; 3 | } 4 | 5 | EventListener.prototype.addEventListener = function (name, callback) { 6 | this.__listeners__.push({callback: callback, name: name}); 7 | } 8 | 9 | EventListener.prototype.removeEventListener = function (name, callback) { 10 | for (var i = this.__listeners__.length - 1; i >= 0; i--) { 11 | if (this.__listeners__[i].name === name && this.__listeners__[i].callback === callback) { 12 | this.__listeners__.splice(i, 1); 13 | } 14 | } 15 | } 16 | 17 | EventListener.prototype.dispatchEvent = function (name) { 18 | for (var i = this.__listeners__.length - 1; i >= 0; i--) { 19 | if (this.__listeners__[(this.__listeners__.length - i - 1)].name === name) { 20 | this.__listeners__[(this.__listeners__.length - i - 1)].callback(); 21 | } 22 | } 23 | } 24 | 25 | module.exports = EventListener; -------------------------------------------------------------------------------- /test/helpers/jsdom.js: -------------------------------------------------------------------------------- 1 | var jsdom = require('jsdom'); 2 | 3 | jsdom.createPage = function (url, html, scriptSources, callback) { 4 | if (typeof scriptSources === 'function' && callback === undefined) { 5 | callback = scriptSources 6 | scriptSources = undefined; 7 | } 8 | jsdom.env({ 9 | url: url, 10 | html: html, 11 | virtualConsole: jsdom.createVirtualConsole().sendTo(console), 12 | src: scriptSources, 13 | done: function (err, window) { 14 | if (err) throw err; 15 | window.open = function () { }; 16 | callback(window); 17 | } 18 | }); 19 | } 20 | 21 | jsdom.injectScript = function (document, scriptSource, callback) { 22 | var script = document.createElement('script'); 23 | script.onload = function () { 24 | callback(); 25 | } 26 | script.text = scriptSource; 27 | document.body.appendChild(script); 28 | } 29 | 30 | module.exports = jsdom; 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appnexus-html5-lib", 3 | "description": "AppNexus HTML5 Client Library", 4 | "version": "1.4.1", 5 | "main": "src/client.js", 6 | "author": { 7 | "name": "AppNexus", 8 | "url": "http://www.appnexus.com" 9 | }, 10 | "license": "Apache-2.0", 11 | "scripts": { 12 | "build": "grunt build", 13 | "watch": "grunt watch", 14 | "test": "grunt build && mocha --reporter spec", 15 | "preversion": "npm run test && git add dist && git checkout dist/", 16 | "postversion": "npm run build && git add dist/ && git commit --amend --no-edit && git push origin master && git push --tags || true" 17 | }, 18 | "devDependencies": { 19 | "callsite": "^1.0.0", 20 | "chai": "^4.1.2", 21 | "grunt": "^0.4.5", 22 | "grunt-browserify": "^4.0.1", 23 | "grunt-cli": "^0.1.13", 24 | "grunt-contrib-clean": "^0.7.0", 25 | "grunt-contrib-uglify": "^0.11.0", 26 | "grunt-contrib-watch": "^0.6.1", 27 | "jsdom": "^7.2.2", 28 | "mocha": "*", 29 | "sinon": "^5.0.7" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /test/lib-event-listener-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var EventListener = require('../src/lib/event-listener'); 3 | 4 | describe('EventListener', function () { 5 | var el; 6 | 7 | beforeEach(function () { 8 | el = new EventListener(); 9 | }) 10 | 11 | it('creates an EventListener type object', function () { 12 | expect(el).to.be.an.instanceOf(EventListener); 13 | }); 14 | 15 | it('can add event listenings', function () { 16 | el.addEventListener('click', function () {}); 17 | el.addEventListener('click2', function () {}); 18 | 19 | expect(el.__listeners__.length).to.be.equal(2); 20 | expect(el.__listeners__[0].name).to.be.equal('click'); 21 | }); 22 | 23 | it('can remove event listenings', function () { 24 | var callback = function(){}; 25 | el.addEventListener('click', callback); 26 | el.addEventListener('click2', function () {}); 27 | el.removeEventListener('click', callback); 28 | 29 | expect(el.__listeners__.length).to.be.equal(1); 30 | expect(el.__listeners__[0].name).to.be.equal('click2'); 31 | }); 32 | 33 | it('can dispatch events', function (done) { 34 | el.addEventListener('click', function () { 35 | done(); 36 | }); 37 | el.dispatchEvent('click'); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /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 | }; -------------------------------------------------------------------------------- /test/client-ready-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jsdom = require('./helpers/jsdom'); 3 | var fixtures = require('./helpers/fixtures'); 4 | var Porthole; 5 | var windowProxy; 6 | var windowObject; 7 | 8 | var adData = { 9 | macros: { 10 | 'macro_1': 'lorem', 11 | 'macro_2': 'ipsum', 12 | } 13 | }; 14 | 15 | describe('client events', function () { 16 | beforeEach(function (done) { 17 | jsdom.createPage(fixtures.HTML5_ADVERTISEMENT_URL, fixtures.HTML5_ADVERTISEMENT, [fixtures.LIB_SOURCE_CLIENT], function (window) { 18 | windowObject = window; 19 | global.window = window 20 | Porthole = require('../src/lib/porthole');; 21 | 22 | windowProxy = new Porthole.WindowProxy('http://localhost/'); 23 | done(); 24 | }); 25 | }); 26 | 27 | it('shouldnt trigger APPNEXUS.ready until a message from the porthole happens', function (done) { 28 | windowObject.APPNEXUS.ready(function () { 29 | done('this should not happen'); 30 | }); 31 | done(); 32 | }); 33 | 34 | it('should trigger appnexus ready since a post happend from the porthole with action setAdData and adData object info', function (done) { 35 | windowProxy.post({ action: 'setAdData', parameters: adData }); 36 | 37 | windowObject.APPNEXUS.ready(function () { 38 | done(); 39 | }); 40 | }); 41 | 42 | it('macros should be available right after appnexus.ready is called', function (done) { 43 | windowProxy.post({ action: 'setAdData', parameters: adData }); 44 | 45 | windowObject.APPNEXUS.ready(function () { 46 | expect(windowObject.APPNEXUS.getMacroByName('macro_1')).to.equal('lorem') 47 | expect(windowObject.APPNEXUS.getMacroByName('macro_2')).to.equal('ipsum') 48 | expect(windowObject.APPNEXUS.getMacroByName('macro_undefined')).to.equal(undefined) 49 | done(); 50 | }); 51 | }); 52 | }); -------------------------------------------------------------------------------- /test/helpers/fixtures.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var libSourceClient = fs.readFileSync(path.resolve(__dirname, '../../dist/appnexus-html5-lib.js'), 'utf-8'); 4 | 5 | if (!libSourceClient) throw new Error('Unable to load "' + path.resolve(__dirname, '../../dist/appnexus-html5-lib.js') + '"') 6 | 7 | var clickUrl = 'http://www.example.com'; 8 | 9 | 10 | module.exports = { 11 | HTML5_WEBPAGE: '', 12 | HTML5_ADVERTISEMENT: '', 13 | HTML5_CLICK_URL: clickUrl, 14 | HTML5_ADVERTISEMENT_URL: 'http://vcdn.adnxs.com/p/creative-html/fe/c6/98/2e/fec6982e-d54b-4f89-91bb-08a9e5299abb?clickTag=' + encodeURIComponent(clickUrl), 15 | LIB_SOURCE_CLIENT: libSourceClient, 16 | EXPANDING_PROPERTIES_JSON: '{"data":{"action":"set-expand-properties","properties":{"width":600,"height":500,"floating":true,"interstitial":true,"expand":{"easing":"lineas","duration":500},"collapse":{"easing":"ease-in-out","duration":1000}}},"sourceOrigin":"http://vcdn.adnxs.com","targetOrigin":"*","sourceWindowName":"an-1623518d-9ead-92c5-1a4d-83770e5e048f","targetWindowName":""}', 17 | CLICK_MESSAGE: '{"data":{"action":"click"},"sourceOrigin":"http://vcdn.adnxs.com","targetOrigin":"*","sourceWindowName":"nodejs","targetWindowName":""}', 18 | SET_EXPAND_PROPS_MESSAGE: '{"data":{"action":"set-expand-properties","properties":{"width":600,"height":800}},"sourceOrigin":"http://vcdn.adnxs.com","targetOrigin":"*","sourceWindowName":"nodejs","targetWindowName":""}', 19 | EXPAND_MESSAGE: '{"data":{"action":"expand"},"sourceOrigin":"http://vcdn.adnxs.com","targetOrigin":"*","sourceWindowName":"nodejs","targetWindowName":""}', 20 | COLLAPSE_MESSAGE: '{"data":{"action":"collapse"},"sourceOrigin":"http://vcdn.adnxs.com","targetOrigin":"*","sourceWindowName":"nodejs","targetWindowName":""}' 21 | }; -------------------------------------------------------------------------------- /examples/standard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HTML5 Standard: 300x250 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 23 |
24 | Powering 25 | the advertising 26 | that powers 27 | the Internet 28 |
29 |
30 | 31 | 32 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /examples/interstitial/styles/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Arial, sans-serif; 3 | margin: 0px; 4 | } 5 | 6 | #container { 7 | position: absolute; 8 | display: block; 9 | border-style:solid; 10 | height: 60%; 11 | border-width: thin; 12 | background-color: black; 13 | cursor: pointer; 14 | width: 75%; 15 | } 16 | 17 | .centered { 18 | position: absolute; 19 | margin: auto; 20 | top: 0; 21 | left: 0; 22 | bottom: 0; 23 | right: 0; 24 | } 25 | .tagline { 26 | padding: 0; 27 | position: relative; 28 | float: right; 29 | } 30 | .tagline .text { 31 | display: block; 32 | color: #ffffff; 33 | font-size: 48px; 34 | font-weight: bold; 35 | text-align: left; 36 | animation: fadein 2s; 37 | -moz-animation: fadein 2s; /* Firefox */ 38 | -webkit-animation: fadein 2s; /* Safari and Chrome */ 39 | -o-animation: fadein 2s; /* Opera */ 40 | } 41 | strong { 42 | color: #FF8700; 43 | } 44 | #trademark { 45 | color: #FF8700; 46 | font-size: 20px; 47 | vertical-align: top; 48 | } 49 | .top-row { 50 | margin-bottom: 80px; 51 | } 52 | .logo { 53 | left: 0; 54 | margin: 20px; 55 | position: relative; 56 | display: inline-block; 57 | } 58 | .close-button { 59 | font-size: 50px; 60 | font-weight: bold; 61 | color: white; 62 | float: right; 63 | padding: 50px; 64 | display: inline-block; 65 | } 66 | .close-button:hover { 67 | color: #FF8700; 68 | } 69 | 70 | @media (max-width: 1000px) { 71 | #container { 72 | width: 900px; 73 | } 74 | } 75 | 76 | 77 | @media (max-width: 768px) { 78 | #container { 79 | width: 90%; 80 | } 81 | .logo > img { 82 | width: 100px; 83 | } 84 | .tagline .text { 85 | font-size: 30px; 86 | } 87 | .close-button { 88 | font-size: 20px; 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /examples/standard/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Helvetica,Arial,sans-serif; 5 | text-align: center; 6 | overflow:hidden; 7 | } 8 | .container { 9 | overflow: hidden; 10 | background-color: #000000; 11 | height: 248px; 12 | width: 298px; 13 | border: 1px solid #666666; 14 | cursor: pointer; 15 | } 16 | .tagline { 17 | margin: 20px; 18 | padding: 0; 19 | position: relative; 20 | bottom: -26px; 21 | } 22 | .tagline .text { 23 | display: block; 24 | color: #ffffff; 25 | font-size: 28px; 26 | font-weight: 100; 27 | text-align: left; 28 | animation: fadein 2s; 29 | -moz-animation: fadein 2s; /* Firefox */ 30 | -webkit-animation: fadein 2s; /* Safari and Chrome */ 31 | -o-animation: fadein 2s; /* Opera */ 32 | } 33 | strong { 34 | color: #FF8700; 35 | } 36 | .logo { 37 | left: 0; 38 | margin: 20px; 39 | position: relative; 40 | } 41 | .logo img { 42 | width: 115px; 43 | } 44 | .slinky { 45 | left: 149px; 46 | margin: 0; 47 | position: absolute; 48 | top: 1px; 49 | } 50 | .slinky img { 51 | width: 150px; 52 | } 53 | #trademark { 54 | color: #FF8700; 55 | font-size: 20px; 56 | vertical-align: top; 57 | } 58 | 59 | .hidden { 60 | display: none; 61 | } 62 | 63 | @keyframes fadein { 64 | from { 65 | opacity:0; 66 | } 67 | to { 68 | opacity:1; 69 | } 70 | } 71 | @-moz-keyframes fadein { /* Firefox */ 72 | from { 73 | opacity:0; 74 | } 75 | to { 76 | opacity:1; 77 | } 78 | } 79 | @-webkit-keyframes fadein { /* Safari and Chrome */ 80 | from { 81 | opacity:0; 82 | } 83 | to { 84 | opacity:1; 85 | } 86 | } 87 | @-o-keyframes fadein { /* Opera */ 88 | from { 89 | opacity:0; 90 | } 91 | to { 92 | opacity: 1; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /examples/interstitial/js/script.js: -------------------------------------------------------------------------------- 1 | APPNEXUS.ready(function () { 2 | var videoIsMuted = false; 3 | var video = document.getElementById("video"); 4 | 5 | var playPauseButton = document.getElementById("playpause"); 6 | var soundMuteButton = document.getElementById("sound"); 7 | 8 | var playImage = document.getElementById("play"); 9 | var pauseImage = document.getElementById("pause"); 10 | var footerDiv = document.getElementById("footer"); 11 | var logoDov = document.getElementById("logo"); 12 | var closeButton = document.getElementById("close-btn"); 13 | 14 | // video play/pause action 15 | playPauseButton.addEventListener('click', function () { 16 | if(!video.paused) { 17 | video.pause(); 18 | playImage.style.visibility = "visible"; 19 | pauseImage.style.visibility = "hidden"; 20 | }else{ 21 | video.play(); 22 | pauseImage.style.visibility = "visible"; 23 | playImage.style.visibility = "hidden"; 24 | } 25 | isPlaying = !isPlaying; 26 | }); 27 | 28 | // video mute action 29 | soundMuteButton.addEventListener('click', function () { 30 | if(!videoIsMuted) { 31 | video.muted = true; 32 | document.getElementById("mute").style.visibility = "visible"; 33 | document.getElementById("unmute").style.visibility = "hidden"; 34 | }else{ 35 | video.muted = false; 36 | document.getElementById("unmute").style.visibility = "visible"; 37 | document.getElementById("mute").style.visibility = "hidden"; 38 | } 39 | videoIsMuted = !videoIsMuted; 40 | }); 41 | 42 | // close button actions 43 | closeButton.addEventListener("click", function () { 44 | video.pause(); 45 | APPNEXUS.collapse(); 46 | }); 47 | 48 | footerDiv.addEventListener('click', function () { APPNEXUS.click(); }); 49 | logoDov.addEventListener('click', function () { APPNEXUS.click(); }); 50 | 51 | // video ended actions 52 | video.addEventListener("ended", function () { 53 | APPNEXUS.collapse(); 54 | }); 55 | 56 | // request expand and overlay 57 | APPNEXUS.setExpandProperties({ 58 | interstitial : true 59 | }); 60 | APPNEXUS.expand(); 61 | }); 62 | -------------------------------------------------------------------------------- /examples/interstitial/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | HTML5 Interstitial 965x600 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 19 | 20 | X 21 | 22 |
23 |
24 | Powering 25 | the advertising 26 | that powers 27 | the Internet 28 |
29 |
30 | 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/expanding-push/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTML5 Expanding Push 300x250 4 | 5 | 6 | 7 | 8 | 9 | 10 | 22 | 23 | 24 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /examples/expanding/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | HTML5 Expanding 300x250 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /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). -------------------------------------------------------------------------------- /examples/expanding/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Helvetica,Arial,sans-serif; 5 | text-align: center; 6 | overflow:hidden; 7 | } 8 | .container { 9 | overflow: hidden; 10 | background-color: #000000; 11 | height: 248px; 12 | width: 298px; 13 | border: 1px solid #666666; 14 | cursor: pointer; 15 | } 16 | .tagline { 17 | margin: 20px; 18 | padding: 0; 19 | position: relative; 20 | bottom: -26px; 21 | } 22 | .tagline .text { 23 | display: block; 24 | color: #ffffff; 25 | font-size: 28px; 26 | font-weight: 100; 27 | text-align: left; 28 | animation: fadein 2s; 29 | -moz-animation: fadein 2s; /* Firefox */ 30 | -webkit-animation: fadein 2s; /* Safari and Chrome */ 31 | -o-animation: fadein 2s; /* Opera */ 32 | } 33 | strong { 34 | color: #FF8700; 35 | } 36 | .logo { 37 | left: 0; 38 | margin: 20px; 39 | position: relative; 40 | } 41 | .logo img { 42 | width: 115px; 43 | } 44 | .slinky { 45 | left: 149px; 46 | margin: 0; 47 | position: absolute; 48 | top: 1px; 49 | } 50 | .slinky img { 51 | width: 150px; 52 | } 53 | #trademark { 54 | color: #FF8700; 55 | font-size: 20px; 56 | vertical-align: top; 57 | } 58 | .expandedContainer { 59 | animation: containerExpandDown 2s; 60 | -moz-animation: containerExpandDown 2s; 61 | -webkit-animation: containerExpandDown 2s; 62 | -o-animation: containerExpandDown 2s; 63 | 64 | height: 600px; 65 | } 66 | .expandedTagline { 67 | animation: taglineExpandDown 2s; 68 | -moz-animation: taglineExpandDown 2s; 69 | -webkit-animation: taglineExpandDown 2s; 70 | -o-animation: taglineExpandDown 2s; 71 | 72 | bottom: -380px; 73 | } 74 | 75 | .hidden { 76 | display: none; 77 | } 78 | 79 | .overlay { 80 | position: absolute; 81 | top: 0; 82 | left: 0; 83 | width: 100%; 84 | height: 100%; 85 | z-index: 100; 86 | background-color: rgba(0,0,0,0.5); /*dim the background*/ 87 | } 88 | 89 | .interstitial { 90 | margin: 0 auto; 91 | height: 480px; 92 | width: 320px; 93 | } 94 | 95 | .interstitial .tagline { 96 | bottom: -255px; 97 | } 98 | 99 | @keyframes fadein { 100 | from { 101 | opacity:0; 102 | } 103 | to { 104 | opacity:1; 105 | } 106 | } 107 | @-moz-keyframes fadein { /* Firefox */ 108 | from { 109 | opacity:0; 110 | } 111 | to { 112 | opacity:1; 113 | } 114 | } 115 | @-webkit-keyframes fadein { /* Safari and Chrome */ 116 | from { 117 | opacity:0; 118 | } 119 | to { 120 | opacity:1; 121 | } 122 | } 123 | @-o-keyframes fadein { /* Opera */ 124 | from { 125 | opacity:0; 126 | } 127 | to { 128 | opacity: 1; 129 | } 130 | } 131 | 132 | @keyframes containerExpandDown { 133 | from { 134 | height: 300px; 135 | } 136 | to { 137 | height: 600px; 138 | } 139 | } 140 | 141 | @keyframes taglineExpandDown { 142 | from { 143 | bottom: -76px; 144 | } 145 | to { 146 | bottom: -380px; 147 | } 148 | } -------------------------------------------------------------------------------- /examples/expanding-push/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: Helvetica,Arial,sans-serif; 5 | text-align: center; 6 | overflow:hidden; 7 | } 8 | .container { 9 | overflow: hidden; 10 | background-color: #000000; 11 | height: 248px; 12 | width: 298px; 13 | border: 1px solid #666666; 14 | cursor: pointer; 15 | 16 | } 17 | .tagline { 18 | margin: 20px; 19 | padding: 0; 20 | position: relative; 21 | bottom: -26px; 22 | } 23 | .tagline .text { 24 | display: block; 25 | color: #ffffff; 26 | font-size: 28px; 27 | font-weight: 100; 28 | text-align: left; 29 | animation: fadein 2s; 30 | -moz-animation: fadein 2s; /* Firefox */ 31 | -webkit-animation: fadein 2s; /* Safari and Chrome */ 32 | -o-animation: fadein 2s; /* Opera */ 33 | } 34 | strong { 35 | color: #FF8700; 36 | } 37 | .logo { 38 | left: 0; 39 | margin: 20px; 40 | position: relative; 41 | } 42 | .logo img { 43 | width: 115px; 44 | } 45 | .slinky { 46 | left: 149px; 47 | margin: 0; 48 | position: absolute; 49 | top: 1px; 50 | } 51 | .slinky img { 52 | width: 150px; 53 | } 54 | #trademark { 55 | color: #FF8700; 56 | font-size: 20px; 57 | vertical-align: top; 58 | } 59 | .expandedContainer { 60 | animation: containerExpandDown 2s; 61 | -moz-animation: containerExpandDown 2s; 62 | -webkit-animation: containerExpandDown 2s; 63 | -o-animation: containerExpandDown 2s; 64 | 65 | height: 600px; 66 | } 67 | .expandedTagline { 68 | animation: taglineExpandDown 2s; 69 | -moz-animation: taglineExpandDown 2s; 70 | -webkit-animation: taglineExpandDown 2s; 71 | -o-animation: taglineExpandDown 2s; 72 | 73 | bottom: -380px; 74 | } 75 | 76 | .hidden { 77 | display: none; 78 | } 79 | 80 | .overlay { 81 | position: absolute; 82 | top: 0; 83 | left: 0; 84 | width: 100%; 85 | height: 100%; 86 | z-index: 100; 87 | background-color: rgba(0,0,0,0.5); /*dim the background*/ 88 | } 89 | 90 | .interstitial { 91 | margin: 0 auto; 92 | height: 480px; 93 | width: 320px; 94 | } 95 | 96 | .interstitial .tagline { 97 | bottom: -255px; 98 | } 99 | 100 | @keyframes fadein { 101 | from { 102 | opacity:0; 103 | } 104 | to { 105 | opacity:1; 106 | } 107 | } 108 | @-moz-keyframes fadein { /* Firefox */ 109 | from { 110 | opacity:0; 111 | } 112 | to { 113 | opacity:1; 114 | } 115 | } 116 | @-webkit-keyframes fadein { /* Safari and Chrome */ 117 | from { 118 | opacity:0; 119 | } 120 | to { 121 | opacity:1; 122 | } 123 | } 124 | @-o-keyframes fadein { /* Opera */ 125 | from { 126 | opacity:0; 127 | } 128 | to { 129 | opacity: 1; 130 | } 131 | } 132 | 133 | @keyframes containerExpandDown { 134 | from { 135 | height: 300px; 136 | } 137 | to { 138 | height: 600px; 139 | } 140 | } 141 | 142 | @keyframes taglineExpandDown { 143 | from { 144 | bottom: -76px; 145 | } 146 | to { 147 | bottom: -380px; 148 | } 149 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test/client-test.js: -------------------------------------------------------------------------------- 1 | var expect = require('chai').expect; 2 | var jsdom = require('./helpers/jsdom'); 3 | var sinon = require('sinon'); 4 | var fixtures = require('./helpers/fixtures'); 5 | var Porthole; 6 | 7 | var windowObject; 8 | 9 | describe('appnexus-html5-lib client', function () { 10 | beforeEach(function (done) { 11 | jsdom.createPage(fixtures.HTML5_ADVERTISEMENT_URL, fixtures.HTML5_ADVERTISEMENT, [fixtures.LIB_SOURCE_CLIENT], function (window) { 12 | windowObject = window; 13 | global.window = window 14 | window = window || {}; 15 | 16 | global.document = window.document; 17 | Porthole = require('../src/lib/porthole');; 18 | var windowProxy = new Porthole.WindowProxy('http://localhost'); 19 | 20 | var adData = { 21 | macros: { 22 | 'macro_1': 'lorem', 23 | 'macro_2': 'ipsum', 24 | } 25 | } 26 | windowProxy.post({ action: 'setAdData', parameters: adData }); 27 | 28 | done(); 29 | }); 30 | }); 31 | 32 | it('triggered APPNEXUS.ready', function (done) { 33 | windowObject.APPNEXUS.ready(function () { 34 | done(); 35 | }); 36 | }); 37 | 38 | it('gets correct clickTag from URL', function (done) { 39 | windowObject.APPNEXUS.ready(function () { 40 | expect(windowObject.APPNEXUS.getClickTag()).to.equal(fixtures.HTML5_CLICK_URL); 41 | done(); 42 | }); 43 | }); 44 | 45 | it('check APPNEXUS.click() opens new window', function (done) { 46 | var spy = sinon.spy(windowObject, 'open'); 47 | 48 | windowObject.APPNEXUS.ready(function () { 49 | windowObject.APPNEXUS.click(); 50 | expect(spy.calledOnce).to.equal(true); 51 | done(); 52 | }); 53 | }); 54 | 55 | it('check APPNEXUS.setExpandProperties() sends postMessage to host', function (done) { 56 | var spy = sinon.spy(windowObject, 'postMessage'); 57 | 58 | windowObject.APPNEXUS.ready(function () { 59 | windowObject.APPNEXUS.setExpandProperties({ 60 | width: 600, 61 | height: 800 62 | }); 63 | expect(spy.withArgs(fixtures.SET_EXPAND_PROPS_MESSAGE).calledOnce).to.equal(true); 64 | done(); 65 | }); 66 | }); 67 | 68 | it('check APPNEXUS.getExpandProperties() returns the correct properties', function (done) { 69 | windowObject.APPNEXUS.ready(function () { 70 | windowObject.APPNEXUS.setExpandProperties({ 71 | width: 600, 72 | height: 800 73 | }); 74 | 75 | expect(windowObject.APPNEXUS.getExpandProperties()).to.eql({ width: 600, height: 800 }); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('check APPNEXUS.expand() sends postMessage to host', function (done) { 81 | var spy = sinon.spy(windowObject, 'postMessage'); 82 | 83 | windowObject.APPNEXUS.ready(function () { 84 | windowObject.APPNEXUS.expand(); 85 | expect(spy.withArgs(fixtures.EXPAND_MESSAGE).calledOnce).to.equal(true); 86 | done(); 87 | }); 88 | }); 89 | 90 | it('check APPNEXUS.collapse() sends postMessage to host', function (done) { 91 | var spy = sinon.spy(windowObject, 'postMessage'); 92 | 93 | windowObject.APPNEXUS.ready(function () { 94 | windowObject.APPNEXUS.collapse(); 95 | expect(spy.withArgs(fixtures.COLLAPSE_MESSAGE).calledOnce).to.equal(true); 96 | done(); 97 | }); 98 | }); 99 | 100 | it('should return undefined for a non existing macro', function (done) { 101 | windowObject.APPNEXUS.ready(function () { 102 | expect(windowObject.APPNEXUS.getMacroByName("non_existing_macro")).to.equal(undefined) 103 | done(); 104 | }) 105 | }) 106 | 107 | it('should call getMacroByName once when attempting to require a macro value', function (done) { 108 | var macro = 'blah'; 109 | var spy = sinon.spy(); 110 | sinon.replace(windowObject.APPNEXUS, 'getMacroByName', spy); 111 | 112 | windowObject.APPNEXUS.ready(function () { 113 | windowObject.APPNEXUS.getMacroByName(macro); 114 | expect(spy.withArgs(macro).calledOnce).to.equal(true); 115 | expect(spy.calledOnce).to.be.true; 116 | sinon.restore(); 117 | done(); 118 | }); 119 | }); 120 | }); -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Porthole = require('./lib/porthole'); 4 | var EventListener = require('./lib/event-listener'); 5 | 6 | function AppNexusHTML5Lib () { 7 | var self = this; 8 | this.debug = false; 9 | this.inFrame = false; 10 | this.EventListener = EventListener; 11 | 12 | var isClient = false; 13 | var readyCalled = false; 14 | var isPageLoaded = false; 15 | var expandProperties = {} 16 | var dispatcher = new EventListener(); 17 | var clientPorthole; 18 | var adData = {}; 19 | var clickTag = ''; 20 | 21 | try { 22 | this.inFrame = (window.self !== window.top); 23 | } catch (e) { 24 | this.inFrame = true; 25 | } 26 | 27 | dispatcher.addEventListener('ready', function () { 28 | if (readyCalled) { 29 | if (self.debug) console.info('Client initialized!'); 30 | } 31 | }); 32 | 33 | /** 34 | * Setup porthole so we can talk to our parent and listen to messages from it 35 | */ 36 | var initPorthole = function(){ 37 | clientPorthole = new Porthole.WindowProxy(); 38 | clientPorthole.addEventListener(handleMessages); 39 | clientPorthole.post({ action: 'ready'}); //notify parent we are ready 40 | }; 41 | 42 | var checkReady = function (f){ /in/.test(document.readyState) ? setTimeout(function () { checkReady(f); } , 9) : f(); } 43 | checkReady(function (){ 44 | isPageLoaded = true; 45 | if (!!Object.keys(adData).length) { 46 | dispatcher.dispatchEvent('ready'); 47 | } 48 | }); 49 | 50 | var openUrl = function(url){ 51 | window.open(url, "_blank"); 52 | }; 53 | 54 | /** 55 | * Listen to messages that come from the parent window 56 | * @param messageEvent 57 | */ 58 | var handleMessages = function(messageEvent){ 59 | switch(messageEvent.data.action) { 60 | case 'setAdData': //receive data about the ad 61 | adData = messageEvent.data.parameters; 62 | if (isPageLoaded) { 63 | dispatcher.dispatchEvent('ready'); 64 | } 65 | break; 66 | } 67 | }; 68 | 69 | function getParameterByName(name) { 70 | var match = RegExp('[?&]' + name + 71 | '=([^&]*)').exec(window.location.search); 72 | return match && decodeURIComponent(match[1].replace(/\+/g, ' ')); 73 | } 74 | 75 | this.ready = function (callback) { 76 | if (typeof callback === 'function') { 77 | dispatcher.addEventListener('ready', callback); 78 | } 79 | 80 | if (!readyCalled) { 81 | initPorthole(); 82 | readyCalled = true; 83 | clickTag = this.getClickTag(); 84 | self.debug = !self.inFrame; 85 | } 86 | } 87 | 88 | this.getClickTag = function(){ 89 | return getParameterByName('clickTag'); 90 | }; 91 | 92 | this.click = function () { 93 | clickTag = this.getClickTag(); 94 | if (!clickTag) console.log('No clickTag defined: click event will open a blank page'); 95 | openUrl(clickTag); 96 | if (self.debug) console.info('Client send action: click'); 97 | } 98 | 99 | this.setExpandProperties = function (props) { 100 | if (!readyCalled || !clientPorthole) throw new Error('APPNEXUS library has not been initialized. APPNEXUS.ready() must be called first'); 101 | expandProperties = props; 102 | clientPorthole.post({ action: 'set-expand-properties', properties: props }); 103 | if (self.debug) console.info('Client send action: set-expand-properties'); 104 | } 105 | 106 | this.getExpandProperties = function () { 107 | return expandProperties; 108 | } 109 | 110 | this.expand = function () { 111 | if (!readyCalled || !clientPorthole) throw new Error('APPNEXUS library has not been initialized. APPNEXUS.ready() must be called first'); 112 | clientPorthole.post({ action: 'expand' }); 113 | if (self.debug) console.info('Client send action: expand'); 114 | } 115 | 116 | this.collapse = function () { 117 | if (!readyCalled || !clientPorthole) throw new Error('APPNEXUS library has not been initialized. APPNEXUS.ready() must be called first'); 118 | clientPorthole.post({ action: 'collapse' }); 119 | if (self.debug) console.info('Client send action: collapse'); 120 | } 121 | 122 | this.getMacroByName = function (macro) { 123 | if (!readyCalled || !clientPorthole) throw new Error('APPNEXUS library has not been initialized. APPNEXUS.ready() must be called first'); 124 | 125 | if(adData && adData.macros && adData.macros[macro] !== undefined){ 126 | return adData.macros[macro]; 127 | } 128 | else { 129 | return undefined; 130 | } 131 | } 132 | } 133 | 134 | var APPNEXUS = new AppNexusHTML5Lib(); 135 | if (typeof window !== 'undefined') { 136 | window.APPNEXUS = APPNEXUS; 137 | } 138 | 139 | module.exports = APPNEXUS; -------------------------------------------------------------------------------- /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.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/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 `