├── .gitignore ├── README.md ├── book.png ├── examples ├── __init__.py ├── static │ ├── avatar.jpg │ ├── camera-stock-150.jpg │ ├── css │ │ ├── bootstrap.css │ │ └── styles.css │ ├── deferred-avatars.js │ ├── deferred.js │ ├── easyxdm │ │ ├── easyXDM.Widgets.debug.js │ │ ├── easyXDM.Widgets.js │ │ ├── easyXDM.Widgets.min.js │ │ ├── easyXDM.debug.js │ │ ├── easyXDM.js │ │ ├── easyXDM.min.js │ │ └── name.html │ ├── grey-1x1.png │ ├── hiro │ │ ├── bootstrap.css │ │ ├── example.js │ │ ├── hiro.js │ │ ├── icon.jpg │ │ ├── img │ │ │ ├── glyphicons-halflings-white.png │ │ │ └── glyphicons-halflings.png │ │ ├── jquery.js │ │ ├── phantom.js │ │ ├── underscore.js │ │ ├── webui.css │ │ └── webui.js │ ├── jquery.js │ ├── json2.js │ ├── loading.gif │ ├── partly_cloudy.png │ ├── prototype.js │ ├── qunit │ │ ├── qunit.css │ │ └── qunit.js │ └── sdk.js └── templates │ ├── 10 │ ├── hiro │ │ └── index.html │ └── qunit │ │ └── index.html │ ├── 01 │ ├── json-and-prototypejs │ │ └── index.html │ └── weather-widget │ │ └── index.html │ ├── 02 │ ├── loading-files │ │ ├── index.html │ │ ├── lib.js │ │ └── styles.css │ ├── passing-args │ │ ├── data-attr.js │ │ ├── fragment.js │ │ ├── globals.js │ │ ├── index.html │ │ └── querystring.js │ └── script-blocking │ │ ├── index.html │ │ └── removeSpinner.js │ ├── 03 │ ├── rendering-html │ │ ├── append.js │ │ ├── docwrite.js │ │ └── index.html │ ├── rendering-iframes │ │ ├── iframe.html │ │ └── index.html │ └── rendering-multiple │ │ ├── index.html │ │ └── widget.js │ ├── 04 │ ├── cors │ │ └── index.html │ ├── jsonp │ │ └── index.html │ └── subdomain-proxy │ │ ├── index.html │ │ ├── products.json │ │ └── proxy.html │ ├── 05 │ ├── hash-transport │ │ ├── client.html │ │ └── server.html │ ├── name-transport │ │ ├── client.html │ │ ├── empty.html │ │ └── server.html │ └── postmessage │ │ ├── client.html │ │ └── server.html │ ├── 06 │ ├── auth-new-window │ │ ├── auth.html │ │ ├── iframe.html │ │ ├── index.html │ │ ├── success.html │ │ └── widget.js │ └── detect-cookies │ │ ├── iframe.html │ │ └── index.html │ ├── 07 │ ├── clickjacking │ │ ├── iframe.html │ │ └── index.html │ ├── simple-xss │ │ └── index.html │ └── xss-css │ │ ├── iframe-safe.html │ │ ├── iframe-vulnerable.html │ │ └── index.html │ ├── 08 │ ├── event-listeners │ │ └── index.html │ └── web-service-api │ │ ├── index.html │ │ ├── lib.js │ │ ├── sdk.js │ │ └── tunnel.html │ ├── 09 │ ├── deferred-avatars │ │ └── index.html │ └── deferred-widget │ │ └── index.html │ ├── example.html │ ├── index.html │ ├── layout.html │ └── shared │ ├── easyXDM.js │ └── script.js ├── freeze.py └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | 3 | build 4 | *.pyc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Third-party JavaScript - the code # 2 | 3 | This repository contains companion source code to [Third-party JavaScript](http://thirdpartyjs.com), written by Ben Vinegar and Anton Kovalyov and available via [Manning Publishing](http://manning.com/vinegar). 4 | 5 | The examples correspond to material from the book, and mostly illustrate third-party scripting concepts. 6 | 7 | Feedback and comments are welcome. 8 | 9 | ![Third-party JavaScript](http://github.com/thirdpartyjs/thirdpartyjs-code/raw/master/book.png) 10 | 11 | ## Install instructions 12 | 13 | You'll need both Python and its packaging library, setuptools, in order to build and serve the example code. On OS X, these come pre-installed. On Windows? See Windows Instructions first. 14 | 15 | 1) Install the Frozen-Flask Python package. 16 | 17 | ```$ easy_install Frozen-Flask``` 18 | 19 | 2) Build the source code examples: 20 | 21 | ```$ python freeze.py``` 22 | 23 | 3) Add the following entries to your hosts file: 24 | 25 | ``` 26 | 127.0.0.1 publisher.dev 27 | 127.0.0.1 proxy.publisher.dev 28 | 127.0.0.1 widget.dev 29 | ``` 30 | 31 | 4) Run the embedded server: 32 | 33 | ```$ python server.py``` 34 | 35 | Once the server is running, access the examples through http://publisher.dev:5000/examples/index.html. 36 | 37 | 38 | 39 | ## Installing Python on Windows 40 | 41 | Download and install Python 2.7.x from the Python download page. 42 | 43 | Next, setuptools. Download and run the 2.7.x installer from: http://pypi.python.org/pypi/setuptools#downloads 44 | 45 | Afterwards, you'll need to add both the Python directory (C:\Python27) and the scripts directory (C:\Python27\Scripts) to your PATH. To do this, go to My Computer ‣ Properties ‣ Advanced ‣ Environment Variables. 46 | 47 | You should now be able to run the 'python' and 'easy_install' executables from the Windows shell. Success! 48 | 49 | ## Need help? 50 | 51 | Visit Manning's Author Online forums for this book: http://www.manning-sandbox.com/forum.jspa?forumID=791 52 | -------------------------------------------------------------------------------- /book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/book.png -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- 1 | import string 2 | from flask import Flask, render_template, make_response, url_for 3 | from werkzeug.routing import BaseConverter 4 | 5 | app = Flask(__name__, static_url_path='/common') 6 | 7 | app.config.update( 8 | FREEZER_DEFAULT_MIMETYPE='text/html', 9 | FREEZER_DESTINATION='../build', 10 | FREEZER_BASE_URL='/examples', 11 | FREEZER_IGNORE_MIMETYPE_WARNINGS=True 12 | ) 13 | 14 | PUBLISHER_DOMAIN = 'publisher.dev' 15 | 16 | PUBLISHER_URL = 'http://publisher.dev:5000' 17 | PUBLISHER_PROXY_URL = 'http://proxy.publisher.dev:5000' 18 | SERVICE_URL = 'http://widget.dev:5000' 19 | 20 | 21 | class RegexConverter(BaseConverter): 22 | def __init__(self, url_map, *items): 23 | super(RegexConverter, self).__init__(url_map) 24 | self.regex = items[0] 25 | 26 | 27 | app.url_map.converters['regex'] = RegexConverter 28 | 29 | 30 | @app.route('///') 31 | def example(chapter=None, name=None, file=None): 32 | template = "%s/%s/%s" % (chapter, name, file) 33 | 34 | response = make_response(render_template(template)) 35 | 36 | try: 37 | ext = string.split(file, '.')[-1] 38 | if ext == 'js' or ext == 'jsonp': 39 | response.headers['Content-type'] = 'application/x-javascript' 40 | if ext == 'json': 41 | response.headers['Content-type'] = 'application/x-json' 42 | elif ext == 'css': 43 | response.headers['Content-type'] = 'text/css' 44 | except Exception: 45 | pass 46 | 47 | return response 48 | 49 | 50 | @app.route("/index.html") 51 | def index(): 52 | return render_template('index.html') 53 | 54 | 55 | #================================================ 56 | # URL Helpers 57 | #================================================ 58 | 59 | def publisher_url_for(*args, **kwargs): 60 | return PUBLISHER_URL + url_for(*args, **kwargs) 61 | 62 | 63 | def publisher_proxy_url_for(*args, **kwargs): 64 | return PUBLISHER_PROXY_URL + url_for(*args, **kwargs) 65 | 66 | 67 | def service_url_for(*args, **kwargs): 68 | return SERVICE_URL + url_for(*args, **kwargs) 69 | 70 | 71 | @app.context_processor 72 | def inject_url_helpers(): 73 | return { 74 | 'publisher_url_for': publisher_url_for, 75 | 'publisher_proxy_url_for': publisher_proxy_url_for, 76 | 'service_url_for': service_url_for, 77 | 78 | 'publisher_url': PUBLISHER_URL, 79 | 'publisher_proxy_url': PUBLISHER_PROXY_URL, 80 | 'service_url': SERVICE_URL, 81 | 82 | 'publisher_domain': PUBLISHER_DOMAIN 83 | } 84 | 85 | 86 | # You can run this application as a server for debugging purposes. 87 | 88 | if __name__ == '__main__': 89 | app.debug = True 90 | app.run() 91 | -------------------------------------------------------------------------------- /examples/static/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/avatar.jpg -------------------------------------------------------------------------------- /examples/static/camera-stock-150.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/camera-stock-150.jpg -------------------------------------------------------------------------------- /examples/static/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 20px; 3 | } 4 | .return-to { 5 | margin-bottom: 20px; 6 | } 7 | 8 | .footer { 9 | font-size: 12px; 10 | margin-top: 3em; 11 | } -------------------------------------------------------------------------------- /examples/static/deferred-avatars.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | 3 | function getWindowDimensions() { 4 | var documentElement = document.documentElement; 5 | 6 | return ('pageYOffset' in window) ? 7 | { 8 | // W3C 9 | scrollTop: window.pageYOffset, 10 | scrollLeft: window.pageXOffset, 11 | height: window.innerHeight, 12 | width: window.innerWidth 13 | } : { 14 | // IE 8 and below 15 | scrollTop: documentElement.scrollTop, 16 | scrollLeft: documentElement.scrollLeft, 17 | height: documentElement.clientHeight, 18 | width: documentElement.clientWidth 19 | }; 20 | } 21 | 22 | function getPosition(el) { 23 | var left = 0; 24 | var top = 0; 25 | while (el && el.offsetParent) { 26 | left += el.offsetLeft; 27 | top += el.offsetTop; 28 | el = el.offsetParent; 29 | } 30 | return { top: top, left: left }; 31 | } 32 | 33 | function insideViewport(el) { 34 | var win = getWindowDimensions(); 35 | var pos = getPosition(el); 36 | 37 | var top = pos.top; 38 | var bot = pos.top + el.offsetHeight; 39 | 40 | return bot >= win.scrollTop && 41 | top <= win.scrollTop + win.height; 42 | } 43 | 44 | function throttle(el, name, handler, delay) { 45 | var last = (new Date()).getTime(); 46 | 47 | function wrapper(ev) { 48 | var now = (new Date()).getTime(); 49 | if (now - last >= delay) { 50 | last = now; 51 | handler(ev); 52 | } 53 | } 54 | 55 | if (el.addEventListener) 56 | return el.addEventListener(name, wrapper, false); 57 | else 58 | return el.attachEvent('on' + name, wrapper); 59 | } 60 | 61 | function displayDeferredAvatars() { 62 | var PADDING = 200; 63 | 64 | var all = document.querySelectorAll('img.dsq-deferred-avatar'); 65 | var avatars = []; 66 | 67 | for (var i = 0; i < all.length; i++) { 68 | if (all[i].offsetParent) { 69 | avatars.push(all[i]); 70 | } 71 | } 72 | 73 | if (!avatars.length) { 74 | return; 75 | } 76 | 77 | var win = getWindowDimensions(); 78 | 79 | function clipsViewport(el) { 80 | var pos = getPosition(el); 81 | var top = pos.top; 82 | var bot = pos.top + el.offsetHeight; 83 | 84 | if (bot <= win.scrollTop - PADDING) { 85 | return -1; 86 | } else if (top >= win.scrollTop + win.height + PADDING) { 87 | return 1; 88 | } else { 89 | return 0; 90 | } 91 | } 92 | 93 | var pivot = (function() { 94 | var high = avatars.length; 95 | var low = 0; 96 | var clip; 97 | var i; 98 | 99 | while (low < high) { 100 | i = parseInt((low + high) / 2, 10); 101 | clip = clipsViewport(avatars[i]); 102 | 103 | if (clip === -1) { // above 104 | low = i + 1; 105 | } else if (clip === 1) { // below 106 | high = i; 107 | } else { 108 | return i; 109 | } 110 | } 111 | 112 | return -1; 113 | })(); 114 | 115 | if (pivot === -1) { 116 | return; 117 | } 118 | 119 | function displayIfVisible(i) { 120 | var el = avatars[i]; 121 | 122 | if (clipsViewport(el) !== 0) { 123 | return false; 124 | } 125 | 126 | el.setAttribute('src', el.getAttribute('data-dsq-src')); 127 | el.className = ''; 128 | el.removeAttribute('data-dsq-src'); 129 | 130 | return true; 131 | } 132 | 133 | for (i = pivot; i >= 0 && displayIfVisible(i); i--) {} 134 | for (i = pivot + 1; 135 | i < avatars.length && displayIfVisible(i); 136 | i++) {} 137 | } 138 | 139 | throttle(window, 'scroll', displayDeferredAvatars, 250); 140 | })(window); -------------------------------------------------------------------------------- /examples/static/deferred.js: -------------------------------------------------------------------------------- 1 | (function (window, undefined) { 2 | 3 | function getWindowDimensions() { 4 | var documentElement = document.documentElement; 5 | 6 | return ('pageYOffset' in window) ? 7 | { 8 | // W3C 9 | scrollTop: window.pageYOffset, 10 | scrollLeft: window.pageXOffset, 11 | height: window.innerHeight, 12 | width: window.innerWidth 13 | } : { 14 | // IE 8 and below 15 | scrollTop: documentElement.scrollTop, 16 | scrollLeft: documentElement.scrollLeft, 17 | height: documentElement.clientHeight, 18 | width: documentElement.clientWidth 19 | }; 20 | } 21 | 22 | function getPosition(el) { 23 | var left = 0; 24 | var top = 0; 25 | while (el && el.offsetParent) { 26 | left += el.offsetLeft; 27 | top += el.offsetTop; 28 | el = el.offsetParent; 29 | } 30 | return { top: top, left: left }; 31 | } 32 | 33 | function insideViewport(el) { 34 | var win = getWindowDimensions(); 35 | var pos = getPosition(el); 36 | 37 | var top = pos.top; 38 | var bot = pos.top + el.offsetHeight; 39 | 40 | return bot >= win.scrollTop && 41 | top <= win.scrollTop + win.height; 42 | } 43 | 44 | function whenVisible(callback) { 45 | var el = document.getElementById('stork-container'); 46 | 47 | function listener() { 48 | if (insideViewport(el)) { 49 | callback(); 50 | } 51 | } 52 | 53 | debounce(window, 'scroll', listener, 250); 54 | listener(); 55 | } 56 | 57 | function debounce(el, name, handler, delay) { 58 | var exec; 59 | 60 | function wrapper(ev) { 61 | if (exec) { 62 | clearTimeout(exec); 63 | } 64 | 65 | exec = setTimeout(function () { 66 | handler(ev); 67 | }, delay); 68 | } 69 | 70 | if (el.addEventListener) 71 | return el.addEventListener(name, wrapper, false); 72 | else 73 | return el.attachEvent('on' + name, wrapper); 74 | } 75 | 76 | whenVisible(function () { 77 | alert('In viewport'); 78 | }); 79 | })(window); -------------------------------------------------------------------------------- /examples/static/easyxdm/easyXDM.Widgets.debug.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true, immed: true, passfail: true, undef: true, newcap: true*/ 2 | /*global easyXDM, window */ 3 | /** 4 | * easyXDM 5 | * http://easyxdm.net/ 6 | * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | /** 27 | * @class easyXDM.WidgetManager 28 | * A class for managing widgets.
29 | * Handles initializing widgets, and does all of the message distribution. 30 |

 31 |  _widgetManager = new easyXDM.WidgetManager({
 32 |  local: "../hash.html",
 33 |  container: document.getElementById("defaultcontainer")
 34 |  },function(){
 35 |  _widgetManager.addWidget("http://provider.easyxdm.net/example/widget.html",{});
 36 |  });
 37 |  
38 | * Widgets can by dynamically added using the addWidget method 39 |

 40 |  _widgetManager.addWidget("http://provider.easyxdm.net/example/widget.html",{
 41 |  container document.getElementById("widgetcontainer")
 42 |  });
 43 |  
44 | * @constructor 45 | * @param {Object} config The WidgetManagers configuration 46 | * @namespace easyXDM 47 | */ 48 | easyXDM.WidgetManager = function(config){ 49 | var WidgetManager = this, _hashUrl = config.local, _channelNr = 0; 50 | var Events = { 51 | WidgetInitialized: "widgetinitialized", 52 | WidgetFailed: "widgetfailed" 53 | }; 54 | var _widgets = {}, _subscribers = {}; 55 | var _widgetSettings = { 56 | hosturl: location.href 57 | }; 58 | easyXDM.apply(_widgetSettings, config.widgetSettings); 59 | var _container = config.container || document.body; 60 | 61 | /** 62 | * @private 63 | * Raises the specified event 64 | * @param {String} event The raised event 65 | * @param {Object} arg 66 | */ 67 | function _raiseEvent(event, arg){ 68 | if (config.listeners && config.listeners.event) { 69 | config.listeners.event(WidgetManager, arg); 70 | } 71 | } 72 | 73 | /** 74 | * @private 75 | * Adds the widghet to the list of subscribers for the given topic 76 | * @param {String} url The widgets url 77 | * @param {String} topic The topic to subscribe to 78 | */ 79 | function _subscribe(url, topic){ 80 | if (!(topic in _subscribers)) { 81 | _subscribers[topic] = []; 82 | } 83 | _subscribers[topic].push(url); 84 | } 85 | 86 | /** 87 | * @private 88 | * Initialized the widget.
89 | * This is called after the widget has notified that it is ready. 90 | * @param {Object} widget The widget 91 | * @param {String} url The widgets url 92 | * @param {Object} widgetConfig The widgets configuration 93 | */ 94 | function _initializeWidget(widget, url, widgetConfig){ 95 | widget.initialize(_widgetSettings, function(response){ 96 | if (response.isInitialized) { 97 | _widgets[url] = widget; 98 | var i = response.subscriptions.length; 99 | while (i--) { 100 | _subscribe(url, response.subscriptions[i]); 101 | } 102 | _raiseEvent(Events.WidgetInitialized, { 103 | url: url 104 | }); 105 | } 106 | else { 107 | widget.destroy(); 108 | _raiseEvent(Events.WidgetFailed, { 109 | url: url 110 | }); 111 | } 112 | }); 113 | } 114 | 115 | /** 116 | * @private 117 | * Publishes the data to the topics subscribers 118 | * @param {String} url The senders url 119 | * @param {String} topic The datas topic 120 | * @param {Object} data The data to publish 121 | */ 122 | function _publish(url, topic, data){ 123 | var subscribers = _subscribers[topic]; 124 | if (subscribers) { 125 | var i = subscribers.length, widgetUrl; 126 | while (i--) { 127 | widgetUrl = subscribers[i]; 128 | if (widgetUrl !== url) { 129 | _widgets[widgetUrl].send(url, topic, data); 130 | } 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * @private 137 | * Sets up a new widget 138 | * @param {String} url The widgets url 139 | * @param {Object} widgetConfig The widgets configuration 140 | */ 141 | function _setUpWidget(url, widgetConfig){ 142 | var widget = new easyXDM.Rpc({ 143 | channel: "widget" + _channelNr++, 144 | local: _hashUrl, 145 | remote: url, 146 | container: widgetConfig.container || _container, 147 | swf: config.swf, 148 | onReady: function(){ 149 | _initializeWidget(widget, url, widgetConfig); 150 | } 151 | }, { 152 | local: { 153 | subscribe: { 154 | isVoid: true, 155 | method: function(topic){ 156 | _subscribe(url, topic); 157 | } 158 | }, 159 | publish: { 160 | isVoid: true, 161 | method: function(topic, data){ 162 | _publish(url, topic, data); 163 | } 164 | } 165 | }, 166 | remote: { 167 | initialize: {}, 168 | send: { 169 | isVoid: true 170 | } 171 | } 172 | }); 173 | } 174 | 175 | /** 176 | * Adds a widget to the collection 177 | * @param {String} url The url to load the widget from 178 | * @param {Object} widgetConfig The widgets url 179 | */ 180 | this.addWidget = function(url, widgetConfig){ 181 | if (url in _widgets) { 182 | throw new Error("A widget with this url has already been initialized"); 183 | } 184 | _setUpWidget(url, widgetConfig); 185 | }; 186 | 187 | /** 188 | * Removes the widget 189 | * @param {Object} url 190 | */ 191 | this.removeWidget = function(url){ 192 | if (url in _widgets) { 193 | for (var topic in _subscribers) { 194 | if (_subscribers.hasOwnProperty(topic)) { 195 | var subscribers = _subscribers[topic], i = subscribers.length; 196 | while (i--) { 197 | if (subscribers[i] === url) { 198 | subscribers.splice(i, 1); 199 | break; 200 | } 201 | } 202 | } 203 | } 204 | _widgets[url].destroy(); 205 | delete _widgets[url]; 206 | } 207 | }; 208 | 209 | /** 210 | * Publish data to a topics subscribers 211 | * @param {String} topic The topic to publish to 212 | * @param {Object} data The data to publish 213 | */ 214 | this.publish = function(topic, data){ 215 | _publish("", topic, data); 216 | }; 217 | 218 | /** 219 | * Broadcasts data to all the widgets 220 | * @param {Object} data The data to broadcast 221 | */ 222 | this.broadcast = function(data){ 223 | for (var url in _widgets) { 224 | if (_widgets.hasOwnPropert(url)) { 225 | _widgets[url].send({ 226 | url: "", 227 | topic: "broadcast", 228 | data: data 229 | }); 230 | } 231 | } 232 | }; 233 | }; 234 | 235 | /** 236 | * @class easyXDM.Widget 237 | * The base framework for creating widgets 238 | * @constructor 239 | * @param {Object} config The widgets configuration 240 | * @param {Function} onReady A method to run after the widget has been initialized. 241 | * @namespace easyXDM 242 | */ 243 | easyXDM.Widget = function(config){ 244 | var _widget = this; 245 | var _incomingMessageHandler; 246 | var _widgetHost = new easyXDM.Rpc({ 247 | swf: config.swf 248 | }, { 249 | remote: { 250 | subscribe: { 251 | isVoid: true 252 | }, 253 | publish: { 254 | isVoid: true 255 | } 256 | }, 257 | local: { 258 | initialize: { 259 | method: function(settings){ 260 | config.initialized(_widget, _widgetHost); 261 | return { 262 | isInitialized: true, 263 | subscriptions: config.subscriptions 264 | }; 265 | } 266 | }, 267 | send: { 268 | isVoid: true, 269 | method: function(url, topic, data){ 270 | _incomingMessageHandler(url, topic, data); 271 | } 272 | } 273 | } 274 | }); 275 | 276 | /** 277 | * @private 278 | * Destroy the interface on unload 279 | */ 280 | window.onunload = function(){ 281 | _widgetHost.destroy(); 282 | }; 283 | 284 | /** 285 | * Publish data to subscribers to a topic 286 | * @param {String} topic The topic to publish to 287 | * @param {Object} data The data to publish 288 | */ 289 | this.publish = function(topic, data){ 290 | _widgetHost.publish(topic, data); 291 | }; 292 | 293 | /** 294 | * Subscribe to a topic 295 | * @param {String} topic The topic to subscribe to 296 | */ 297 | this.subscribe = function(topic){ 298 | _widgetHost.subscribe(topic); 299 | }; 300 | 301 | /** 302 | * Register the method that will handle incoming messages 303 | * @param {Function} fn The handler 304 | */ 305 | this.registerMessageHandler = function(fn){ 306 | _incomingMessageHandler = fn; 307 | }; 308 | 309 | config.initialize(this, _widgetHost); 310 | }; 311 | -------------------------------------------------------------------------------- /examples/static/easyxdm/easyXDM.Widgets.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true, immed: true, passfail: true, undef: true, newcap: true*/ 2 | /*global easyXDM, window */ 3 | /** 4 | * easyXDM 5 | * http://easyxdm.net/ 6 | * Copyright(c) 2009-2011, Øyvind Sean Kinsey, oyvind@kinsey.no. 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | /** 27 | * @class easyXDM.WidgetManager 28 | * A class for managing widgets.
29 | * Handles initializing widgets, and does all of the message distribution. 30 |

 31 |  _widgetManager = new easyXDM.WidgetManager({
 32 |  local: "../hash.html",
 33 |  container: document.getElementById("defaultcontainer")
 34 |  },function(){
 35 |  _widgetManager.addWidget("http://provider.easyxdm.net/example/widget.html",{});
 36 |  });
 37 |  
38 | * Widgets can by dynamically added using the addWidget method 39 |

 40 |  _widgetManager.addWidget("http://provider.easyxdm.net/example/widget.html",{
 41 |  container document.getElementById("widgetcontainer")
 42 |  });
 43 |  
44 | * @constructor 45 | * @param {Object} config The WidgetManagers configuration 46 | * @namespace easyXDM 47 | */ 48 | easyXDM.WidgetManager = function(config){ 49 | var WidgetManager = this, _hashUrl = config.local, _channelNr = 0; 50 | var Events = { 51 | WidgetInitialized: "widgetinitialized", 52 | WidgetFailed: "widgetfailed" 53 | }; 54 | var _widgets = {}, _subscribers = {}; 55 | var _widgetSettings = { 56 | hosturl: location.href 57 | }; 58 | easyXDM.apply(_widgetSettings, config.widgetSettings); 59 | var _container = config.container || document.body; 60 | 61 | /** 62 | * @private 63 | * Raises the specified event 64 | * @param {String} event The raised event 65 | * @param {Object} arg 66 | */ 67 | function _raiseEvent(event, arg){ 68 | if (config.listeners && config.listeners.event) { 69 | config.listeners.event(WidgetManager, arg); 70 | } 71 | } 72 | 73 | /** 74 | * @private 75 | * Adds the widghet to the list of subscribers for the given topic 76 | * @param {String} url The widgets url 77 | * @param {String} topic The topic to subscribe to 78 | */ 79 | function _subscribe(url, topic){ 80 | if (!(topic in _subscribers)) { 81 | _subscribers[topic] = []; 82 | } 83 | _subscribers[topic].push(url); 84 | } 85 | 86 | /** 87 | * @private 88 | * Initialized the widget.
89 | * This is called after the widget has notified that it is ready. 90 | * @param {Object} widget The widget 91 | * @param {String} url The widgets url 92 | * @param {Object} widgetConfig The widgets configuration 93 | */ 94 | function _initializeWidget(widget, url, widgetConfig){ 95 | widget.initialize(_widgetSettings, function(response){ 96 | if (response.isInitialized) { 97 | _widgets[url] = widget; 98 | var i = response.subscriptions.length; 99 | while (i--) { 100 | _subscribe(url, response.subscriptions[i]); 101 | } 102 | _raiseEvent(Events.WidgetInitialized, { 103 | url: url 104 | }); 105 | } 106 | else { 107 | widget.destroy(); 108 | _raiseEvent(Events.WidgetFailed, { 109 | url: url 110 | }); 111 | } 112 | }); 113 | } 114 | 115 | /** 116 | * @private 117 | * Publishes the data to the topics subscribers 118 | * @param {String} url The senders url 119 | * @param {String} topic The datas topic 120 | * @param {Object} data The data to publish 121 | */ 122 | function _publish(url, topic, data){ 123 | var subscribers = _subscribers[topic]; 124 | if (subscribers) { 125 | var i = subscribers.length, widgetUrl; 126 | while (i--) { 127 | widgetUrl = subscribers[i]; 128 | if (widgetUrl !== url) { 129 | _widgets[widgetUrl].send(url, topic, data); 130 | } 131 | } 132 | } 133 | } 134 | 135 | /** 136 | * @private 137 | * Sets up a new widget 138 | * @param {String} url The widgets url 139 | * @param {Object} widgetConfig The widgets configuration 140 | */ 141 | function _setUpWidget(url, widgetConfig){ 142 | var widget = new easyXDM.Rpc({ 143 | channel: "widget" + _channelNr++, 144 | local: _hashUrl, 145 | remote: url, 146 | container: widgetConfig.container || _container, 147 | swf: config.swf, 148 | onReady: function(){ 149 | _initializeWidget(widget, url, widgetConfig); 150 | } 151 | }, { 152 | local: { 153 | subscribe: { 154 | isVoid: true, 155 | method: function(topic){ 156 | _subscribe(url, topic); 157 | } 158 | }, 159 | publish: { 160 | isVoid: true, 161 | method: function(topic, data){ 162 | _publish(url, topic, data); 163 | } 164 | } 165 | }, 166 | remote: { 167 | initialize: {}, 168 | send: { 169 | isVoid: true 170 | } 171 | } 172 | }); 173 | } 174 | 175 | /** 176 | * Adds a widget to the collection 177 | * @param {String} url The url to load the widget from 178 | * @param {Object} widgetConfig The widgets url 179 | */ 180 | this.addWidget = function(url, widgetConfig){ 181 | if (url in _widgets) { 182 | throw new Error("A widget with this url has already been initialized"); 183 | } 184 | _setUpWidget(url, widgetConfig); 185 | }; 186 | 187 | /** 188 | * Removes the widget 189 | * @param {Object} url 190 | */ 191 | this.removeWidget = function(url){ 192 | if (url in _widgets) { 193 | for (var topic in _subscribers) { 194 | if (_subscribers.hasOwnProperty(topic)) { 195 | var subscribers = _subscribers[topic], i = subscribers.length; 196 | while (i--) { 197 | if (subscribers[i] === url) { 198 | subscribers.splice(i, 1); 199 | break; 200 | } 201 | } 202 | } 203 | } 204 | _widgets[url].destroy(); 205 | delete _widgets[url]; 206 | } 207 | }; 208 | 209 | /** 210 | * Publish data to a topics subscribers 211 | * @param {String} topic The topic to publish to 212 | * @param {Object} data The data to publish 213 | */ 214 | this.publish = function(topic, data){ 215 | _publish("", topic, data); 216 | }; 217 | 218 | /** 219 | * Broadcasts data to all the widgets 220 | * @param {Object} data The data to broadcast 221 | */ 222 | this.broadcast = function(data){ 223 | for (var url in _widgets) { 224 | if (_widgets.hasOwnPropert(url)) { 225 | _widgets[url].send({ 226 | url: "", 227 | topic: "broadcast", 228 | data: data 229 | }); 230 | } 231 | } 232 | }; 233 | }; 234 | 235 | /** 236 | * @class easyXDM.Widget 237 | * The base framework for creating widgets 238 | * @constructor 239 | * @param {Object} config The widgets configuration 240 | * @param {Function} onReady A method to run after the widget has been initialized. 241 | * @namespace easyXDM 242 | */ 243 | easyXDM.Widget = function(config){ 244 | var _widget = this; 245 | var _incomingMessageHandler; 246 | var _widgetHost = new easyXDM.Rpc({ 247 | swf: config.swf 248 | }, { 249 | remote: { 250 | subscribe: { 251 | isVoid: true 252 | }, 253 | publish: { 254 | isVoid: true 255 | } 256 | }, 257 | local: { 258 | initialize: { 259 | method: function(settings){ 260 | config.initialized(_widget, _widgetHost); 261 | return { 262 | isInitialized: true, 263 | subscriptions: config.subscriptions 264 | }; 265 | } 266 | }, 267 | send: { 268 | isVoid: true, 269 | method: function(url, topic, data){ 270 | _incomingMessageHandler(url, topic, data); 271 | } 272 | } 273 | } 274 | }); 275 | 276 | /** 277 | * @private 278 | * Destroy the interface on unload 279 | */ 280 | window.onunload = function(){ 281 | _widgetHost.destroy(); 282 | }; 283 | 284 | /** 285 | * Publish data to subscribers to a topic 286 | * @param {String} topic The topic to publish to 287 | * @param {Object} data The data to publish 288 | */ 289 | this.publish = function(topic, data){ 290 | _widgetHost.publish(topic, data); 291 | }; 292 | 293 | /** 294 | * Subscribe to a topic 295 | * @param {String} topic The topic to subscribe to 296 | */ 297 | this.subscribe = function(topic){ 298 | _widgetHost.subscribe(topic); 299 | }; 300 | 301 | /** 302 | * Register the method that will handle incoming messages 303 | * @param {Function} fn The handler 304 | */ 305 | this.registerMessageHandler = function(fn){ 306 | _incomingMessageHandler = fn; 307 | }; 308 | 309 | config.initialize(this, _widgetHost); 310 | }; 311 | -------------------------------------------------------------------------------- /examples/static/easyxdm/easyXDM.Widgets.min.js: -------------------------------------------------------------------------------- 1 | easyXDM.WidgetManager=function(e){var h=this,b=e.local,k=0;var n={WidgetInitialized:"widgetinitialized",WidgetFailed:"widgetfailed"};var j={},d={};var i={hosturl:location.href};easyXDM.apply(i,e.widgetSettings);var g=e.container||document.body;function f(p,o){if(e.listeners&&e.listeners.event){e.listeners.event(h,o)}}function c(p,o){if(!(o in d)){d[o]=[]}d[o].push(p)}function m(p,o,q){p.initialize(i,function(r){if(r.isInitialized){j[o]=p;var s=r.subscriptions.length;while(s--){c(o,r.subscriptions[s])}f(n.WidgetInitialized,{url:o})}else{p.destroy();f(n.WidgetFailed,{url:o})}})}function a(p,o,r){var s=d[o];if(s){var q=s.length,t;while(q--){t=s[q];if(t!==p){j[t].send(p,o,r)}}}}function l(o,q){var p=new easyXDM.Rpc({channel:"widget"+k++,local:b,remote:o,container:q.container||g,swf:e.swf,onReady:function(){m(p,o,q)}},{local:{subscribe:{isVoid:true,method:function(r){c(o,r)}},publish:{isVoid:true,method:function(r,s){a(o,r,s)}}},remote:{initialize:{},send:{isVoid:true}}})}this.addWidget=function(o,p){if(o in j){throw new Error("A widget with this url has already been initialized")}l(o,p)};this.removeWidget=function(p){if(p in j){for(var o in d){if(d.hasOwnProperty(o)){var r=d[o],q=r.length;while(q--){if(r[q]===p){r.splice(q,1);break}}}}j[p].destroy();delete j[p]}};this.publish=function(o,p){a("",o,p)};this.broadcast=function(p){for(var o in j){if(j.hasOwnPropert(o)){j[o].send({url:"",topic:"broadcast",data:p})}}}};easyXDM.Widget=function(c){var a=this;var b;var d=new easyXDM.Rpc({swf:c.swf},{remote:{subscribe:{isVoid:true},publish:{isVoid:true}},local:{initialize:{method:function(e){c.initialized(a,d);return{isInitialized:true,subscriptions:c.subscriptions}}},send:{isVoid:true,method:function(f,e,g){b(f,e,g)}}}});window.onunload=function(){d.destroy()};this.publish=function(e,f){d.publish(e,f)};this.subscribe=function(e){d.subscribe(e)};this.registerMessageHandler=function(e){b=e};c.initialize(this,d)}; -------------------------------------------------------------------------------- /examples/static/easyxdm/easyXDM.min.js: -------------------------------------------------------------------------------- 1 | (function(J,c,l,G,g,D){var b=this;var j=Math.floor(Math.random()*10000);var m=Function.prototype;var M=/^((http.?:)\/\/([^:\/\s]+)(:\d+)*)/;var N=/[\-\w]+\/\.\.\//;var B=/([^:])\/\//g;var E="";var k={};var I=J.easyXDM;var Q="easyXDM_";var A;var u=false;function y(U,W){var V=typeof U[W];return V=="function"||(!!(V=="object"&&U[W]))||V=="unknown"}function q(U,V){return !!(typeof(U[V])=="object"&&U[V])}function n(U){return Object.prototype.toString.call(U)==="[object Array]"}function S(V){try{var U=new ActiveXObject(V);U=null;return true}catch(W){return false}}var r,t;if(y(J,"addEventListener")){r=function(W,U,V){W.addEventListener(U,V,false)};t=function(W,U,V){W.removeEventListener(U,V,false)}}else{if(y(J,"attachEvent")){r=function(U,W,V){U.attachEvent("on"+W,V)};t=function(U,W,V){U.detachEvent("on"+W,V)}}else{throw new Error("Browser not supported")}}var T=false,F=[],H;if("readyState" in c){H=c.readyState;T=H=="complete"||(~navigator.userAgent.indexOf("AppleWebKit/")&&(H=="loaded"||H=="interactive"))}else{T=!!c.body}function o(){if(T){return}T=true;for(var U=0;U')}else{W=c.createElement("IFRAME");W.name=U.props.name}W.id=W.name=U.props.name;delete U.props.name;if(U.onLoad){r(W,"load",U.onLoad)}if(typeof U.container=="string"){U.container=c.getElementById(U.container)}if(!U.container){W.style.position="absolute";W.style.top="-2000px";U.container=c.body}var V=U.props.src;delete U.props.src;P(W,U.props);W.border=W.frameBorder=0;U.container.appendChild(W);W.src=V;U.props.src=V;return W}function R(X,W){if(typeof X=="string"){X=[X]}var V,U=X.length;while(U--){V=X[U];V=new RegExp(V.substr(0,1)=="^"?V:("^"+V.replace(/(\*)/g,".$1").replace(/\?/g,".")+"$"));if(V.test(W)){return true}}return false}function h(W){var ab=W.protocol,V;W.isHost=W.isHost||p(O.xdm_p);u=W.hash||false;if(!W.props){W.props={}}if(!W.isHost){W.channel=O.xdm_c;W.secret=O.xdm_s;W.remote=O.xdm_e;ab=O.xdm_p;if(W.acl&&!R(W.acl,W.remote)){throw new Error("Access denied for "+W.remote)}}else{W.remote=x(W.remote);W.channel=W.channel||"default"+j++;W.secret=Math.random().toString(16).substring(2);if(p(ab)){if(f(l.href)==f(W.remote)){ab="4"}else{if(y(J,"postMessage")||y(c,"postMessage")){ab="1"}else{if(y(J,"ActiveXObject")&&S("ShockwaveFlash.ShockwaveFlash")){ab="6"}else{if(navigator.product==="Gecko"&&"frameElement" in J&&navigator.userAgent.indexOf("WebKit")==-1){ab="5"}else{if(W.remoteHelper){W.remoteHelper=x(W.remoteHelper);ab="2"}else{ab="0"}}}}}}}switch(ab){case"0":P(W,{interval:100,delay:2000,useResize:true,useParent:false,usePolling:false},true);if(W.isHost){if(!W.local){var Z=l.protocol+"//"+l.host,U=c.body.getElementsByTagName("img"),aa;var X=U.length;while(X--){aa=U[X];if(aa.src.substring(0,Z.length)===Z){W.local=aa.src;break}}if(!W.local){W.local=J}}var Y={xdm_c:W.channel,xdm_p:0};if(W.local===J){W.usePolling=true;W.useParent=true;W.local=l.protocol+"//"+l.host+l.pathname+l.search;Y.xdm_e=W.local;Y.xdm_pa=1}else{Y.xdm_e=x(W.local)}if(W.container){W.useResize=false;Y.xdm_po=1}W.remote=L(W.remote,Y)}else{P(W,{channel:O.xdm_c,remote:O.xdm_e,useParent:!p(O.xdm_pa),usePolling:!p(O.xdm_po),useResize:W.useParent?false:W.useResize})}V=[new k.stack.HashTransport(W),new k.stack.ReliableBehavior({}),new k.stack.QueueBehavior({encode:true,maxLength:4000-W.remote.length}),new k.stack.VerifyBehavior({initiate:W.isHost})];break;case"1":V=[new k.stack.PostMessageTransport(W)];break;case"2":V=[new k.stack.NameTransport(W),new k.stack.QueueBehavior(),new k.stack.VerifyBehavior({initiate:W.isHost})];break;case"3":V=[new k.stack.NixTransport(W)];break;case"4":V=[new k.stack.SameOriginTransport(W)];break;case"5":V=[new k.stack.FrameElementTransport(W)];break;case"6":if(!W.swf){W.swf="../../tools/easyxdm.swf"}V=[new k.stack.FlashTransport(W)];break}V.push(new k.stack.QueueBehavior({lazy:W.lazy,remove:true}));return V}function z(X){var Y,W={incoming:function(aa,Z){this.up.incoming(aa,Z)},outgoing:function(Z,aa){this.down.outgoing(Z,aa)},callback:function(Z){this.up.callback(Z)},init:function(){this.down.init()},destroy:function(){this.down.destroy()}};for(var V=0,U=X.length;V<\/script>')}}};(function(){var U={};k.Fn={set:function(V,W){U[V]=W},get:function(W,V){var X=U[W];if(V){delete U[W]}return X}}}());k.Socket=function(V){var U=z(h(V).concat([{incoming:function(Y,X){V.onMessage(Y,X)},callback:function(X){if(V.onReady){V.onReady(X)}}}])),W=f(V.remote);this.origin=f(V.remote);this.destroy=function(){U.destroy()};this.postMessage=function(X){U.outgoing(X,W)};U.init()};k.Rpc=function(W,V){if(V.local){for(var Y in V.local){if(V.local.hasOwnProperty(Y)){var X=V.local[Y];if(typeof X==="function"){V.local[Y]={method:X}}}}}var U=z(h(W).concat([new k.stack.RpcBehavior(this,V),{callback:function(Z){if(W.onReady){W.onReady(Z)}}}]));this.origin=f(W.remote);this.destroy=function(){U.destroy()};U.init()};k.stack.SameOriginTransport=function(V){var W,Y,X,U;return(W={outgoing:function(aa,ab,Z){X(aa);if(Z){Z()}},destroy:function(){if(Y){Y.parentNode.removeChild(Y);Y=null}},onDOMReady:function(){U=f(V.remote);if(V.isHost){P(V.props,{src:L(V.remote,{xdm_e:l.protocol+"//"+l.host+l.pathname,xdm_c:V.channel,xdm_p:4}),name:Q+V.channel+"_provider"});Y=w(V);k.Fn.set(V.channel,function(Z){X=Z;G(function(){W.up.callback(true)},0);return function(aa){W.up.incoming(aa,U)}})}else{X=i().Fn.get(V.channel,true)(function(Z){W.up.incoming(Z,U)});G(function(){W.up.callback(true)},0)}},init:function(){C(W.onDOMReady,W)}})};k.stack.FlashTransport=function(X){var Z,U,Y,aa,V,ab;function ac(ae,ad){G(function(){Z.up.incoming(ae,aa)},0)}function W(ag){var ad=X.swf+"?host="+X.isHost;var af="easyXDM_swf_"+Math.floor(Math.random()*10000);k.Fn.set("flash_loaded",function(){k.stack.FlashTransport.__swf=V=ab.firstChild;ag()});ab=c.createElement("div");P(ab.style,{height:"1px",width:"1px",postition:"abosolute",left:0,top:0});c.body.appendChild(ab);var ae="proto="+b.location.protocol+"&domain="+v(b.location.href)+"&ns="+E;ab.innerHTML=""}return(Z={outgoing:function(ae,af,ad){V.postMessage(X.channel,ae.toString());if(ad){ad()}},destroy:function(){try{V.destroyChannel(X.channel)}catch(ad){}V=null;if(U){U.parentNode.removeChild(U);U=null}},onDOMReady:function(){aa=X.remote;V=k.stack.FlashTransport.__swf;k.Fn.set("flash_"+X.channel+"_init",function(){G(function(){Z.up.callback(true)})});k.Fn.set("flash_"+X.channel+"_onMessage",ac);var ad=function(){V.createChannel(X.channel,X.secret,f(X.remote),X.isHost);if(X.isHost){P(X.props,{src:L(X.remote,{xdm_e:f(l.href),xdm_c:X.channel,xdm_p:6,xdm_s:X.secret}),name:Q+X.channel+"_provider"});U=w(X)}};if(V){ad()}else{W(ad)}},init:function(){C(Z.onDOMReady,Z)}})};k.stack.PostMessageTransport=function(X){var Z,aa,V,W;function U(ab){if(ab.origin){return f(ab.origin)}if(ab.uri){return f(ab.uri)}if(ab.domain){return l.protocol+"//"+ab.domain}throw"Unable to retrieve the origin of the event"}function Y(ac){var ab=U(ac);if(ab==W&&ac.data.substring(0,X.channel.length+1)==X.channel+" "){Z.up.incoming(ac.data.substring(X.channel.length+1),ab)}}return(Z={outgoing:function(ac,ad,ab){V.postMessage(X.channel+" "+ac,ad||W);if(ab){ab()}},destroy:function(){t(J,"message",Y);if(aa){V=null;aa.parentNode.removeChild(aa);aa=null}},onDOMReady:function(){W=f(X.remote);if(X.isHost){r(J,"message",function ab(ac){if(ac.data==X.channel+"-ready"){V=("postMessage" in aa.contentWindow)?aa.contentWindow:aa.contentWindow.document;t(J,"message",ab);r(J,"message",Y);G(function(){Z.up.callback(true)},0)}});P(X.props,{src:L(X.remote,{xdm_e:f(l.href),xdm_c:X.channel,xdm_p:1}),name:Q+X.channel+"_provider"});aa=w(X)}else{r(J,"message",Y);V=("postMessage" in J.parent)?J.parent:J.parent.document;V.postMessage(X.channel+"-ready",W);G(function(){Z.up.callback(true)},0)}},init:function(){C(Z.onDOMReady,Z)}})};k.stack.FrameElementTransport=function(V){var W,Y,X,U;return(W={outgoing:function(aa,ab,Z){X.call(this,aa);if(Z){Z()}},destroy:function(){if(Y){Y.parentNode.removeChild(Y);Y=null}},onDOMReady:function(){U=f(V.remote);if(V.isHost){P(V.props,{src:L(V.remote,{xdm_e:f(l.href),xdm_c:V.channel,xdm_p:5}),name:Q+V.channel+"_provider"});Y=w(V);Y.fn=function(Z){delete Y.fn;X=Z;G(function(){W.up.callback(true)},0);return function(aa){W.up.incoming(aa,U)}}}else{if(c.referrer&&f(c.referrer)!=O.xdm_e){J.top.location=O.xdm_e}X=J.frameElement.fn(function(Z){W.up.incoming(Z,U)});W.up.callback(true)}},init:function(){C(W.onDOMReady,W)}})};k.stack.NameTransport=function(Y){var Z;var ab,af,X,ad,ae,V,U;function ac(ai){var ah=Y.remoteHelper+(ab?"#_3":"#_2")+Y.channel;af.contentWindow.sendMessage(ai,ah)}function aa(){if(ab){if(++ad===2||!ab){Z.up.callback(true)}}else{ac("ready");Z.up.callback(true)}}function ag(ah){Z.up.incoming(ah,V)}function W(){if(ae){G(function(){ae(true)},0)}}return(Z={outgoing:function(ai,aj,ah){ae=ah;ac(ai)},destroy:function(){af.parentNode.removeChild(af);af=null;if(ab){X.parentNode.removeChild(X);X=null}},onDOMReady:function(){ab=Y.isHost;ad=0;V=f(Y.remote);Y.local=x(Y.local);if(ab){k.Fn.set(Y.channel,function(ai){if(ab&&ai==="ready"){k.Fn.set(Y.channel,ag);aa()}});U=L(Y.remote,{xdm_e:Y.local,xdm_c:Y.channel,xdm_p:2});P(Y.props,{src:U+"#"+Y.channel,name:Q+Y.channel+"_provider"});X=w(Y)}else{Y.remoteHelper=Y.remote;k.Fn.set(Y.channel,ag)}af=w({props:{src:Y.local+"#_4"+Y.channel},onLoad:function ah(){var ai=af||this;t(ai,"load",ah);k.Fn.set(Y.channel+"_load",W);(function aj(){if(typeof ai.contentWindow.sendMessage=="function"){aa()}else{G(aj,50)}}())}})},init:function(){C(Z.onDOMReady,Z)}})};k.stack.HashTransport=function(W){var Z;var ae=this,ac,X,U,aa,aj,Y,ai;var ad,V;function ah(al){if(!ai){return}var ak=W.remote+"#"+(aj++)+"_"+al;((ac||!ad)?ai.contentWindow:ai).location=ak}function ab(ak){aa=ak;Z.up.incoming(aa.substring(aa.indexOf("_")+1),V)}function ag(){if(!Y){return}var ak=Y.location.href,am="",al=ak.indexOf("#");if(al!=-1){am=ak.substring(al)}if(am&&am!=aa){ab(am)}}function af(){X=setInterval(ag,U)}return(Z={outgoing:function(ak,al){ah(ak)},destroy:function(){J.clearInterval(X);if(ac||!ad){ai.parentNode.removeChild(ai)}ai=null},onDOMReady:function(){ac=W.isHost;U=W.interval;aa="#"+W.channel;aj=0;ad=W.useParent;V=f(W.remote);if(ac){W.props={src:W.remote,name:Q+W.channel+"_provider"};if(ad){W.onLoad=function(){Y=J;af();Z.up.callback(true)}}else{var am=0,ak=W.delay/50;(function al(){if(++am>ak){throw new Error("Unable to reference listenerwindow")}try{Y=ai.contentWindow.frames[Q+W.channel+"_consumer"]}catch(an){}if(Y){af();Z.up.callback(true)}else{G(al,50)}}())}ai=w(W)}else{Y=J;af();if(ad){ai=parent;Z.up.callback(true)}else{P(W,{props:{src:W.remote+"#"+W.channel+new Date(),name:Q+W.channel+"_consumer"},onLoad:function(){Z.up.callback(true)}});ai=w(W)}}},init:function(){C(Z.onDOMReady,Z)}})};k.stack.ReliableBehavior=function(V){var X,Z;var Y=0,U=0,W="";return(X={incoming:function(ac,aa){var ab=ac.indexOf("_"),ad=ac.substring(0,ab).split(",");ac=ac.substring(ab+1);if(ad[0]==Y){W="";if(Z){Z(true)}}if(ac.length>0){X.down.outgoing(ad[1]+","+Y+"_"+W,aa);if(U!=ad[1]){U=ad[1];X.up.incoming(ac,aa)}}},outgoing:function(ac,aa,ab){W=ac;Z=ab;X.down.outgoing(U+","+(++Y)+"_"+ac,aa)}})};k.stack.QueueBehavior=function(W){var Z,aa=[],ad=true,X="",ac,U=0,V=false,Y=false;function ab(){if(W.remove&&aa.length===0){s(Z);return}if(ad||aa.length===0||ac){return}ad=true;var ae=aa.shift();Z.down.outgoing(ae.data,ae.origin,function(af){ad=false;if(ae.callback){G(function(){ae.callback(af)},0)}ab()})}return(Z={init:function(){if(p(W)){W={}}if(W.maxLength){U=W.maxLength;Y=true}if(W.lazy){V=true}else{Z.down.init()}},callback:function(af){ad=false;var ae=Z.up;ab();ae.callback(af)},incoming:function(ah,af){if(Y){var ag=ah.indexOf("_"),ae=parseInt(ah.substring(0,ag),10);X+=ah.substring(ag+1);if(ae===0){if(W.encode){X=g(X)}Z.up.incoming(X,af);X=""}}else{Z.up.incoming(ah,af)}},outgoing:function(ai,af,ah){if(W.encode){ai=D(ai)}var ae=[],ag;if(Y){while(ai.length!==0){ag=ai.substring(0,U);ai=ai.substring(ag.length);ae.push(ag)}while((ag=ae.shift())){aa.push({data:ae.length+"_"+ag,origin:af,callback:ae.length===0?ah:null})}}else{aa.push({data:ai,origin:af,callback:ah})}if(V){Z.down.init()}else{ab()}},destroy:function(){ac=true;Z.down.destroy()}})};k.stack.VerifyBehavior=function(Y){var Z,X,V,W=false;function U(){X=Math.random().toString(16).substring(2);Z.down.outgoing(X)}return(Z={incoming:function(ac,aa){var ab=ac.indexOf("_");if(ab===-1){if(ac===X){Z.up.callback(true)}else{if(!V){V=ac;if(!Y.initiate){U()}Z.down.outgoing(ac)}}}else{if(ac.substring(0,ab)===V){Z.up.incoming(ac.substring(ab+1),aa)}}},outgoing:function(ac,aa,ab){Z.down.outgoing(X+"_"+ac,aa,ab)},callback:function(aa){if(Y.initiate){U()}}})};k.stack.RpcBehavior=function(aa,V){var X,ac=V.serializer||K();var ab=0,Z={};function U(ad){ad.jsonrpc="2.0";X.down.outgoing(ac.stringify(ad))}function Y(ad,af){var ae=Array.prototype.slice;return function(){var ag=arguments.length,ai,ah={method:af};if(ag>0&&typeof arguments[ag-1]==="function"){if(ag>1&&typeof arguments[ag-2]==="function"){ai={success:arguments[ag-2],error:arguments[ag-1]};ah.params=ae.call(arguments,0,ag-2)}else{ai={success:arguments[ag-1]};ah.params=ae.call(arguments,0,ag-1)}Z[""+(++ab)]=ai;ah.id=ab}else{ah.params=ae.call(arguments,0)}if(ad.namedParams&&ah.params.length===1){ah.params=ah.params[0]}U(ah)}}function W(ak,aj,af,ai){if(!af){if(aj){U({id:aj,error:{code:-32601,message:"Procedure not found."}})}return}var ah,ae;if(aj){ah=function(al){ah=m;U({id:aj,result:al})};ae=function(al,am){ae=m;var an={id:aj,error:{code:-32099,message:al}};if(am){an.error.data=am}U(an)}}else{ah=ae=m}if(!n(ai)){ai=[ai]}try{var ad=af.method.apply(af.scope,ai.concat([ah,ae]));if(!p(ad)){ah(ad)}}catch(ag){ae(ag.message)}}return(X={incoming:function(ae,ad){var af=ac.parse(ae);if(af.method){if(V.handle){V.handle(af,U)}else{W(af.method,af.id,V.local[af.method],af.params)}}else{var ag=Z[af.id];if(af.error){if(ag.error){ag.error(af.error)}}else{if(ag.success){ag.success(af.result)}}delete Z[af.id]}},init:function(){if(V.remote){for(var ad in V.remote){if(V.remote.hasOwnProperty(ad)){aa[ad]=Y(V.remote[ad],ad)}}}X.down.init()},destroy:function(){for(var ad in V.remote){if(V.remote.hasOwnProperty(ad)&&aa.hasOwnProperty(ad)){delete aa[ad]}}X.down.destroy()}})};b.easyXDM=k})(window,document,location,window.setTimeout,decodeURIComponent,encodeURIComponent); -------------------------------------------------------------------------------- /examples/static/easyxdm/name.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /examples/static/grey-1x1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/grey-1x1.png -------------------------------------------------------------------------------- /examples/static/hiro/example.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | hiro.module("EmptySuite", { 5 | setUp: function () {}, 6 | waitFor: function () { return true; }, 7 | onTest: function () {} 8 | }); 9 | 10 | hiro.module("BasicTests", { 11 | testSimpleAssertions: function (test) { 12 | test.assertTrue(true); 13 | test.assertFalse(false); 14 | test.assertEqual("Hiro Protagonist", "Hiro Protagonist"); 15 | }, 16 | 17 | testExceptions: function (test) { 18 | test.assertException(function (test) { 19 | throw new Error(); 20 | }, Error); 21 | }, 22 | 23 | testFailedTest: function (test) { 24 | test.assertTrue(false); 25 | }, 26 | 27 | testAsync: function (test) { 28 | test.expect(1); 29 | test.pause(); 30 | 31 | setTimeout(function () { 32 | test.assertTrue(true); 33 | test.resume(); 34 | }, 200); 35 | }, 36 | 37 | testFailedExpect: function (test) { 38 | test.expect(2); 39 | test.assertTrue(true); 40 | } 41 | }); 42 | 43 | hiro.module("NovelTests", { 44 | setUp: function () { 45 | this.loadFixture({ name: "example" }); 46 | }, 47 | 48 | waitFor: function () { 49 | return this.sandbox.document.getElementsByTagName("body").length > 0; 50 | }, 51 | 52 | onTest: function () { 53 | return [ this.sandbox.window, this.sandbox.document ]; 54 | }, 55 | 56 | testTitle: function (test, win, doc) { 57 | test.assertEqual(doc.getElementsByTagName("h1")[0].innerHTML, "Snow Crash"); 58 | }, 59 | 60 | testAuthor: function (test, win, doc) { 61 | test.assertEqual(doc.getElementsByTagName("h2")[0].innerHTML, "by Neal Stephenson"); 62 | } 63 | }); 64 | 65 | hiro.module("FailedSuite", { 66 | onTest: function () { 67 | throw new Error("Hello, World."); 68 | }, 69 | 70 | testSimple: function (test) { 71 | test.assertTrue(true); 72 | } 73 | }); 74 | })(); 75 | -------------------------------------------------------------------------------- /examples/static/hiro/hiro.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Copyright (c) 2012 Anton Kovalyov, http://hirojs.com/ 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining 5 | * a copy of this software and associated documentation files (the 6 | * "Software"), to deal in the Software without restriction, including 7 | * without limitation the rights to use, copy, modify, merge, publish, 8 | * distribute, sublicense, and/or sell copies of the Software, and to 9 | * permit persons to whom the Software is furnished to do so, subject to 10 | * the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be 13 | * included in all copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 22 | */ 23 | 24 | ;(function (window, document) { 25 | "use strict"; 26 | 27 | 28 | 29 | var READY = 0; 30 | var WAITING = 1; 31 | var RUNNING = 2; 32 | var PAUSED = 3; 33 | var DONE = 4; 34 | 35 | var Hiro = function () { 36 | this.status = READY; 37 | this.suites = {}; 38 | this.listeners = { 39 | "hiro.onStart": [], // no arguments 40 | "hiro.onComplete": [], // no arguments 41 | 42 | "suite.onSetup": [], // (suite) 43 | "suite.onStart": [], // (suite) 44 | "suite.onComplete": [], // (suite, success, report) 45 | 46 | "test.onStart": [], // (test) 47 | "test.onComplete": [] // (test, success, report) 48 | }; 49 | }; 50 | 51 | Hiro.prototype = { 52 | bind: function (name, listener) { 53 | if (this.listeners[name] === undefined) 54 | return; 55 | 56 | this.listeners[name].push(listener); 57 | }, 58 | 59 | unbind: function (name, listener) { 60 | if (this.listeners[name] === undefined) 61 | return; 62 | 63 | this.listeners[name] = _.filter(this.listeners[name], function (fn) { 64 | return fn !== listener; 65 | }); 66 | }, 67 | 68 | trigger: function (name, args) { 69 | if (this.listeners[name] === undefined) 70 | return; 71 | 72 | _.each(this.listeners[name], function (fn) { 73 | fn.apply(fn, args); 74 | }); 75 | }, 76 | 77 | attempt: function (fn, obj) { 78 | obj = obj || {}; 79 | 80 | try { 81 | _.bind(fn, obj)(); 82 | } catch (exc) { 83 | return exc; 84 | } 85 | 86 | return null; 87 | }, 88 | 89 | extractStack: function (err, offset) { 90 | var stack; 91 | offset = offset || 3; 92 | 93 | // Try to get location using different hacky methods: 94 | // * For Opera use 'stacktrace' property 95 | // * For Firefox and Chrome use 'stack' property. 96 | // * For Safari and PhantomJS use sourceURL but make 97 | // make sure that it's not self-referencing. 98 | // 99 | // This code was originally borrowed from QUnit. 100 | 101 | if (err.stacktrace) 102 | return err.stacktrace.split("\n")[offset + 3]; 103 | 104 | if (err.stack) { 105 | stack = err.stack.split("\n"); 106 | 107 | if (/^error$/i.test(stack[0])) 108 | stack.shift(); 109 | 110 | return stack[offset]; 111 | } 112 | 113 | if (err.sourceURL) { 114 | if (/hiro.js$/.test(err.sourceURL)) 115 | return; 116 | 117 | return err.sourceURL + ":" + err.line; 118 | } 119 | }, 120 | 121 | getLocation: function (offset) { 122 | try { 123 | throw new Error(); 124 | } catch (err) { 125 | return hiro.extractStack(err, offset); 126 | } 127 | }, 128 | 129 | module: function (name, methods) { 130 | var mixin = []; 131 | 132 | if (_.isArray(methods.mixin)) { 133 | mixin = _.map(methods.mixin, _.bind(function (n) { 134 | if (this.suites[n] === undefined) 135 | return {}; 136 | 137 | return this.suites[n].methods; 138 | }, this)); 139 | 140 | delete methods.mixin; 141 | } 142 | 143 | mixin.splice(0, 0, {}); 144 | mixin.push(methods); 145 | 146 | this.suites[name] = new Suite(name, _.extend.apply(_, mixin)); 147 | }, 148 | 149 | run: function (name) { 150 | var self = this; 151 | 152 | self.status = RUNNING; 153 | 154 | self.attempt(function () { 155 | self.trigger("hiro.onStart"); 156 | }); 157 | 158 | var queue = _.filter(self.suites, function (suite) { 159 | if (name && suite.name !== name) 160 | return; 161 | 162 | return suite; 163 | }); 164 | 165 | var suite = queue.shift(); 166 | var interval = setInterval(function () { 167 | if (suite === null || suite === undefined) { 168 | self.status = DONE; 169 | self.attempt(function () { 170 | self.trigger("hiro.onComplete"); 171 | }); 172 | clearInterval(interval); 173 | return; 174 | } 175 | 176 | switch(suite.status) { 177 | case READY: 178 | suite.prepare(function () { 179 | suite.run(); 180 | }); 181 | break; 182 | case DONE: 183 | if (suite.sandbox) 184 | suite.sandbox.cleanup(); 185 | suite = queue.shift(); 186 | } 187 | }, 100); 188 | } 189 | }; 190 | 191 | 192 | 193 | function Asserts(onFailure) { 194 | this.executed = []; 195 | this.onFailure = onFailure || function () {}; 196 | } 197 | 198 | Asserts.prototype = { 199 | fail: function (name, expected, actual, loc) { 200 | this.onFailure({ 201 | name: name, 202 | expected: expected, 203 | actual: actual, 204 | location: loc 205 | }); 206 | }, 207 | 208 | done: function (name, expected, actual) { 209 | this.executed.push({ 210 | name: name, 211 | expected: expected, 212 | actual: actual 213 | }); 214 | }, 215 | 216 | assertTrue: function (val) { 217 | this[val ? "done" : "fail"]("assertTrue", true, val, hiro.getLocation()); 218 | }, 219 | 220 | assertFalse: function (val) { 221 | this[val ? "fail" : "done"]("assertFalse", false, val, hiro.getLocation()); 222 | }, 223 | 224 | assertEqual: function (expected, actual) { 225 | var ok = _.isEqual(actual, expected); 226 | this[ok ? "done" : "fail"]("assertEqual", expected, actual, hiro.getLocation()); 227 | }, 228 | 229 | assertException: function (func, expected) { 230 | var err = hiro.attempt(func); 231 | var ok = err && err instanceof expected; 232 | this[ok ? "done" : "fail"]("assertException", expected, err || null, hiro.getLocation()); 233 | }, 234 | 235 | assertUndefined: function (val) { 236 | this[val === void 0 ? "done" : "fail"]("assertUndefined", undefined, val, hiro.getLocation()); 237 | }, 238 | 239 | assertNull: function (val) { 240 | this[val === null ? "done" : "fail"]("assertNull", null, val, hiro.getLocation()); 241 | }, 242 | 243 | assertOwnProperty: function (obj, prop) { 244 | var ret = _.has(obj, prop); 245 | this[ret ? "done" : "fail"]("assertOwnProperty", "(object)", ret, hiro.getLocation()); 246 | }, 247 | 248 | assertIndexOf: function (arr, el) { 249 | var ret = _.indexOf(arr, el); 250 | this[ret > -1 ? "done" : "fail"]("assertIndexOf", "> -1", ret, hiro.getLocation()); 251 | } 252 | }; 253 | 254 | 255 | 256 | function Sandbox(opts) { 257 | this.window = null; 258 | this.document = null; 259 | this.frame = null; 260 | this.name = opts.name; 261 | 262 | if (opts.url) { 263 | this.type = "url"; 264 | this.data = opts.url; 265 | return; 266 | } 267 | 268 | var el; 269 | var els = _.union( 270 | _.toArray(document.getElementsByTagName("script")), 271 | _.toArray(document.getElementsByTagName("textarea")) 272 | ); 273 | 274 | el = _.find(els, function (el) { 275 | return el.getAttribute("type") === "hiro/fixture" && 276 | el.getAttribute("data-name") === opts.name; 277 | }); 278 | 279 | if (el) { 280 | this.type = "text"; 281 | this.data = el.tagName.toLowerCase() === "script" ? el.innerHTML : el.value; 282 | } 283 | } 284 | 285 | Sandbox.prototype = { 286 | append: function (container) { 287 | container = container || document.body; 288 | 289 | var frame = document.createElement("iframe"); 290 | var win, doc; 291 | 292 | frame.id = "hiro_fixture_" + this.name; 293 | frame.style.position = "absolute"; 294 | frame.style.top = "-2000px"; 295 | 296 | if (this.type == "url") { 297 | frame.src = this.data; 298 | container.appendChild(frame); 299 | win = frame.contentWindow; 300 | doc = win.document; 301 | } else { 302 | container.appendChild(frame); 303 | win = frame.contentWindow; 304 | doc = win.document; 305 | doc.write(this.data); 306 | doc.close(); 307 | } 308 | 309 | this.window = win; 310 | this.document = doc; 311 | this.frame = frame; 312 | }, 313 | 314 | cleanup: function () { 315 | this.frame.parentNode.removeChild(this.frame); 316 | } 317 | }; 318 | 319 | 320 | 321 | function Suite(name, methods) { 322 | this.name = name; 323 | this.methods = methods; 324 | this.status = READY; 325 | this.queue = []; 326 | this.report = { 327 | success: null, 328 | tests: {} 329 | }; 330 | } 331 | 332 | Suite.prototype = { 333 | loadFixture: function (opts) { 334 | this.sandbox = new Sandbox(opts); 335 | this.sandbox.append(); 336 | 337 | // For backwards compatibility add a reference to 338 | // sandboxed window and document objects to the 339 | // suite itself. 340 | 341 | this.window = this.sandbox.window; 342 | this.document = this.sandbox.document; 343 | }, 344 | 345 | getFixture: function (name) { 346 | var sandbox = new Sandbox({ name: name }); 347 | return sandbox.data; 348 | }, 349 | 350 | prepare: function (onReady) { 351 | onReady = onReady || function () {}; 352 | this.status = WAITING; 353 | 354 | // Execute all listeners for suite.onSetup and pre-emptively 355 | // finish the suite if any of those listeners throws an 356 | // exception. 357 | 358 | var err = hiro.attempt(function () { 359 | hiro.trigger("suite.onSetup", [ this ]); 360 | }, this); 361 | 362 | if (err !== null) 363 | return void this.complete(); 364 | 365 | err = hiro.attempt(function () { 366 | if (_.isFunction(this.methods.setUp)) { 367 | this.methods.setUp.call(this); 368 | } 369 | }, this); 370 | 371 | if (err !== null) 372 | return void this.complete(); 373 | 374 | // Select only functions that start with "test". Only these 375 | // functions will be treated as test cases. Then create a Test 376 | // object for each test method and place it in the queue. 377 | 378 | _.each(this.methods, _.bind(function (func, name) { 379 | if (name.slice(0, 4) !== "test" || !_.isFunction(func)) 380 | return; 381 | 382 | var test = new Test({ name: name, func: func }); 383 | this.queue.push(test); 384 | }, this)); 385 | 386 | // If there is a special method 'waitFor' call it repeatedly 387 | // and wait until it returns true. 388 | 389 | var interval; 390 | if (this.methods.waitFor && _.isFunction(this.methods.waitFor)) { 391 | interval = setInterval(_.bind(function () { 392 | if (this.status !== WAITING) 393 | return; 394 | 395 | if (this.methods.waitFor.apply(this)) { 396 | this.status = READY; 397 | clearInterval(interval); 398 | onReady(); 399 | } 400 | }, this), 25); 401 | 402 | return; 403 | } 404 | 405 | this.status = READY; 406 | onReady(); 407 | }, 408 | 409 | run: function () { 410 | // Execute all listeners for suite.onStart and pre-emptively 411 | // finish the suite if any of those listeners throws an 412 | // exception. 413 | 414 | var err = hiro.attempt(function () { 415 | hiro.trigger("suite.onStart", [ this ]); 416 | }, this); 417 | 418 | if (err !== null) 419 | return void this.complete(); 420 | 421 | this.status = RUNNING; 422 | 423 | var test = this.queue.shift(); 424 | var interval = setInterval(_.bind(function () { 425 | // If there is no more tests to run, declare this suite completed. 426 | 427 | if (test === null || test === undefined) { 428 | this.complete(); 429 | return void clearInterval(interval); 430 | } 431 | 432 | // If there is a test, check its status. If it's still running, keep waiting. 433 | // If it's paused, wait until it goes overtime. And if the test is done, 434 | // get the next one from the queue. 435 | 436 | switch (test.status) { 437 | case READY: 438 | if (_.isFunction(this.methods.onTest)) { 439 | 440 | // Call the onTest method and use its return results as arguments for 441 | // the actual test. If onTest raises an exception--fail the test. 442 | 443 | err = hiro.attempt(function () { 444 | test.args = this.methods.onTest.call(this) || test.args; 445 | }, this); 446 | 447 | if (err !== null) 448 | return void test.fail({ message: err, source: "onTest" }); 449 | } 450 | 451 | test.run(this); 452 | break; 453 | case DONE: 454 | this.report.tests[test.name] = test.report; 455 | test = this.queue.shift(); 456 | break; 457 | default: 458 | // Wait until its done. 459 | } 460 | }, this), 25); 461 | }, 462 | 463 | complete: function () { 464 | var success = _.all(this.report.tests, function (report) { 465 | return report.success; 466 | }); 467 | 468 | this.status = DONE; 469 | hiro.trigger("suite.onComplete", [ this, success, this.report ]); 470 | } 471 | }; 472 | 473 | 474 | 475 | /*jshint devel:true */ 476 | 477 | function Test(opts) { 478 | this.name = opts.name; 479 | this.func = opts.func; 480 | this.timeout = opts.timeout || 250; 481 | this.args = []; 482 | this.status = READY; 483 | this.expected = null; 484 | this.report = { 485 | success: null 486 | }; 487 | 488 | this.asserts = new Asserts(_.bind(function (details) { 489 | this.fail(details); 490 | }, this)); 491 | 492 | // Add shortcuts to all available assertions so that you could 493 | // access them via 'this'. 494 | 495 | _.each(Asserts.prototype, _.bind(function (_, name) { 496 | if (name.slice(0, 6) !== "assert") 497 | return; 498 | 499 | this[name] = function () { 500 | this.asserts[name].apply(this.asserts, arguments); 501 | }; 502 | }, this)); 503 | } 504 | 505 | Test.prototype = { 506 | run: function (context) { 507 | var self = this; 508 | var err; 509 | 510 | // Trigger all test.onStart listeners and fail the test if any of them 511 | // raise an exception. 512 | 513 | err = hiro.attempt(function () { 514 | hiro.trigger("test.onStart", [ self ]); 515 | }); 516 | 517 | if (err !== null) 518 | return void self.fail({ source: "onStart", message: err }); 519 | 520 | // Call the test case function and fail the test if it raises any 521 | // exceptions. If optional context has been provided bind the test 522 | // case to it. 523 | 524 | err = hiro.attempt(function () { 525 | self.func.apply(context || self, _.flatten([self, self.args], true)); 526 | }); 527 | 528 | if (err !== null) 529 | return void self.fail({ source: "Test case", message: err }); 530 | 531 | // If test status is DONE it means that an assertion failed and 532 | // finished the test prematurely. 533 | 534 | if (self.status === DONE) 535 | return; 536 | 537 | // Put the test into a paused mode and set a timer to fail the 538 | // test after certain period of time. 539 | 540 | if (self.status === PAUSED) { 541 | _.delay(function () { 542 | if (self.status === PAUSED) 543 | self.fail({ source: "Test case", message: "Timeout limit exceeded." }); 544 | }, self.timeout); 545 | 546 | return; 547 | } 548 | 549 | // Check that all expected assertions were executed. If self.expected 550 | // is null, user didn't set any expectations so we're golden. 551 | 552 | var exp = self.expected; 553 | var act = self.asserts.executed.length; 554 | 555 | if (exp !== null) { 556 | if (exp !== act) { 557 | self.fail({ 558 | source: "Test case", 559 | message: "Only " + act + " out of " + exp + " assertions were executed." 560 | }); 561 | 562 | return; 563 | } 564 | } 565 | 566 | // Finally, if we're here--declare this test a success and move on. 567 | self.success(); 568 | }, 569 | 570 | expect: function (num) { 571 | this.expected = num; 572 | }, 573 | 574 | pause: function (timeout) { 575 | if (timeout) 576 | this.timeout = timeout; 577 | this.status = PAUSED; 578 | }, 579 | 580 | resume: function () { 581 | if (this.status === DONE) 582 | return; 583 | 584 | this.success(); 585 | }, 586 | 587 | fail: function (details) { 588 | this.status = DONE; 589 | this.report.success = false; 590 | this.report = _.extend(this.report, details); 591 | 592 | hiro.attempt(function () { 593 | hiro.trigger('test.onComplete', [ this, false, this.report ]); 594 | }, this); 595 | }, 596 | 597 | success: function () { 598 | this.status = DONE; 599 | this.report.success = true; 600 | 601 | hiro.attempt(function () { 602 | hiro.trigger('test.onComplete', [ this, true, this.report ]); 603 | }, this); 604 | }, 605 | 606 | toString: function () { 607 | return this.name; 608 | } 609 | }; 610 | 611 | 612 | window.Hiro = Hiro; 613 | })(this, this.document); 614 | -------------------------------------------------------------------------------- /examples/static/hiro/icon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/hiro/icon.jpg -------------------------------------------------------------------------------- /examples/static/hiro/img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/hiro/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /examples/static/hiro/img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/hiro/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /examples/static/hiro/phantom.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true */ 2 | /*global hiro:false, console:false, $:false */ 3 | 4 | (function () { 5 | "use strict"; 6 | 7 | if (!window.haunted) 8 | return; 9 | 10 | // Wraps a Hiro listener and sends its return value 11 | // to the console for PhantomJS to receive. 12 | 13 | function bind(name, callback) { 14 | callback = callback || function () { return {}; }; 15 | 16 | hiro.bind(name, function () { 17 | console.log(JSON.stringify({ 18 | eventName: name, 19 | data: callback.apply({}, arguments) 20 | })); 21 | }); 22 | } 23 | 24 | // Hiro events 25 | 26 | bind("hiro.onStart"); 27 | bind("hiro.onComplete"); 28 | 29 | // Suite specific events 30 | 31 | bind("suite.onSetup", function (suite) { 32 | return { name: suite.name }; 33 | }); 34 | 35 | bind("suite.onStart", function (suite) { 36 | return { name: suite.name }; 37 | }); 38 | 39 | bind("suite.onComplete", function (suite, success) { 40 | return { name: suite.name, success: success }; 41 | }); 42 | 43 | // Test specific events 44 | 45 | bind("test.onStart", function (test) { 46 | return { name: test.toString() }; 47 | }); 48 | 49 | bind("test.onComplete", function (test, success, report) { 50 | if (!success) { 51 | return { 52 | name: test.name, 53 | success: false, 54 | report: report 55 | }; 56 | } 57 | 58 | return { 59 | name: test.toString(), 60 | success: true 61 | }; 62 | }); 63 | })(); 64 | -------------------------------------------------------------------------------- /examples/static/hiro/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | 9 | (function() { 10 | 11 | // Baseline setup 12 | // -------------- 13 | 14 | // Establish the root object, `window` in the browser, or `global` on the server. 15 | var root = this; 16 | 17 | // Save the previous value of the `_` variable. 18 | var previousUnderscore = root._; 19 | 20 | // Establish the object that gets returned to break out of a loop iteration. 21 | var breaker = {}; 22 | 23 | // Save bytes in the minified (but not gzipped) version: 24 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 25 | 26 | // Create quick reference variables for speed access to core prototypes. 27 | var slice = ArrayProto.slice, 28 | unshift = ArrayProto.unshift, 29 | toString = ObjProto.toString, 30 | hasOwnProperty = ObjProto.hasOwnProperty; 31 | 32 | // All **ECMAScript 5** native function implementations that we hope to use 33 | // are declared here. 34 | var 35 | nativeForEach = ArrayProto.forEach, 36 | nativeMap = ArrayProto.map, 37 | nativeReduce = ArrayProto.reduce, 38 | nativeReduceRight = ArrayProto.reduceRight, 39 | nativeFilter = ArrayProto.filter, 40 | nativeEvery = ArrayProto.every, 41 | nativeSome = ArrayProto.some, 42 | nativeIndexOf = ArrayProto.indexOf, 43 | nativeLastIndexOf = ArrayProto.lastIndexOf, 44 | nativeIsArray = Array.isArray, 45 | nativeKeys = Object.keys, 46 | nativeBind = FuncProto.bind; 47 | 48 | // Create a safe reference to the Underscore object for use below. 49 | var _ = function(obj) { return new wrapper(obj); }; 50 | 51 | // Export the Underscore object for **Node.js**, with 52 | // backwards-compatibility for the old `require()` API. If we're in 53 | // the browser, add `_` as a global object via a string identifier, 54 | // for Closure Compiler "advanced" mode. 55 | if (typeof exports !== 'undefined') { 56 | if (typeof module !== 'undefined' && module.exports) { 57 | exports = module.exports = _; 58 | } 59 | exports._ = _; 60 | } else { 61 | root['_'] = _; 62 | } 63 | 64 | // Current version. 65 | _.VERSION = '1.3.1'; 66 | 67 | // Collection Functions 68 | // -------------------- 69 | 70 | // The cornerstone, an `each` implementation, aka `forEach`. 71 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 72 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 73 | var each = _.each = _.forEach = function(obj, iterator, context) { 74 | if (obj == null) return; 75 | if (nativeForEach && obj.forEach === nativeForEach) { 76 | obj.forEach(iterator, context); 77 | } else if (obj.length === +obj.length) { 78 | for (var i = 0, l = obj.length; i < l; i++) { 79 | if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return; 80 | } 81 | } else { 82 | for (var key in obj) { 83 | if (_.has(obj, key)) { 84 | if (iterator.call(context, obj[key], key, obj) === breaker) return; 85 | } 86 | } 87 | } 88 | }; 89 | 90 | // Return the results of applying the iterator to each element. 91 | // Delegates to **ECMAScript 5**'s native `map` if available. 92 | _.map = _.collect = function(obj, iterator, context) { 93 | var results = []; 94 | if (obj == null) return results; 95 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 96 | each(obj, function(value, index, list) { 97 | results[results.length] = iterator.call(context, value, index, list); 98 | }); 99 | if (obj.length === +obj.length) results.length = obj.length; 100 | return results; 101 | }; 102 | 103 | // **Reduce** builds up a single result from a list of values, aka `inject`, 104 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 105 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 106 | var initial = arguments.length > 2; 107 | if (obj == null) obj = []; 108 | if (nativeReduce && obj.reduce === nativeReduce) { 109 | if (context) iterator = _.bind(iterator, context); 110 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 111 | } 112 | each(obj, function(value, index, list) { 113 | if (!initial) { 114 | memo = value; 115 | initial = true; 116 | } else { 117 | memo = iterator.call(context, memo, value, index, list); 118 | } 119 | }); 120 | if (!initial) throw new TypeError('Reduce of empty array with no initial value'); 121 | return memo; 122 | }; 123 | 124 | // The right-associative version of reduce, also known as `foldr`. 125 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 126 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 127 | var initial = arguments.length > 2; 128 | if (obj == null) obj = []; 129 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 130 | if (context) iterator = _.bind(iterator, context); 131 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 132 | } 133 | var reversed = _.toArray(obj).reverse(); 134 | if (context && !initial) iterator = _.bind(iterator, context); 135 | return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); 136 | }; 137 | 138 | // Return the first value which passes a truth test. Aliased as `detect`. 139 | _.find = _.detect = function(obj, iterator, context) { 140 | var result; 141 | any(obj, function(value, index, list) { 142 | if (iterator.call(context, value, index, list)) { 143 | result = value; 144 | return true; 145 | } 146 | }); 147 | return result; 148 | }; 149 | 150 | // Return all the elements that pass a truth test. 151 | // Delegates to **ECMAScript 5**'s native `filter` if available. 152 | // Aliased as `select`. 153 | _.filter = _.select = function(obj, iterator, context) { 154 | var results = []; 155 | if (obj == null) return results; 156 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); 157 | each(obj, function(value, index, list) { 158 | if (iterator.call(context, value, index, list)) results[results.length] = value; 159 | }); 160 | return results; 161 | }; 162 | 163 | // Return all the elements for which a truth test fails. 164 | _.reject = function(obj, iterator, context) { 165 | var results = []; 166 | if (obj == null) return results; 167 | each(obj, function(value, index, list) { 168 | if (!iterator.call(context, value, index, list)) results[results.length] = value; 169 | }); 170 | return results; 171 | }; 172 | 173 | // Determine whether all of the elements match a truth test. 174 | // Delegates to **ECMAScript 5**'s native `every` if available. 175 | // Aliased as `all`. 176 | _.every = _.all = function(obj, iterator, context) { 177 | var result = true; 178 | if (obj == null) return result; 179 | if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); 180 | each(obj, function(value, index, list) { 181 | if (!(result = result && iterator.call(context, value, index, list))) return breaker; 182 | }); 183 | return result; 184 | }; 185 | 186 | // Determine if at least one element in the object matches a truth test. 187 | // Delegates to **ECMAScript 5**'s native `some` if available. 188 | // Aliased as `any`. 189 | var any = _.some = _.any = function(obj, iterator, context) { 190 | iterator || (iterator = _.identity); 191 | var result = false; 192 | if (obj == null) return result; 193 | if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); 194 | each(obj, function(value, index, list) { 195 | if (result || (result = iterator.call(context, value, index, list))) return breaker; 196 | }); 197 | return !!result; 198 | }; 199 | 200 | // Determine if a given value is included in the array or object using `===`. 201 | // Aliased as `contains`. 202 | _.include = _.contains = function(obj, target) { 203 | var found = false; 204 | if (obj == null) return found; 205 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 206 | found = any(obj, function(value) { 207 | return value === target; 208 | }); 209 | return found; 210 | }; 211 | 212 | // Invoke a method (with arguments) on every item in a collection. 213 | _.invoke = function(obj, method) { 214 | var args = slice.call(arguments, 2); 215 | return _.map(obj, function(value) { 216 | return (_.isFunction(method) ? method || value : value[method]).apply(value, args); 217 | }); 218 | }; 219 | 220 | // Convenience version of a common use case of `map`: fetching a property. 221 | _.pluck = function(obj, key) { 222 | return _.map(obj, function(value){ return value[key]; }); 223 | }; 224 | 225 | // Return the maximum element or (element-based computation). 226 | _.max = function(obj, iterator, context) { 227 | if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj); 228 | if (!iterator && _.isEmpty(obj)) return -Infinity; 229 | var result = {computed : -Infinity}; 230 | each(obj, function(value, index, list) { 231 | var computed = iterator ? iterator.call(context, value, index, list) : value; 232 | computed >= result.computed && (result = {value : value, computed : computed}); 233 | }); 234 | return result.value; 235 | }; 236 | 237 | // Return the minimum element (or element-based computation). 238 | _.min = function(obj, iterator, context) { 239 | if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj); 240 | if (!iterator && _.isEmpty(obj)) return Infinity; 241 | var result = {computed : Infinity}; 242 | each(obj, function(value, index, list) { 243 | var computed = iterator ? iterator.call(context, value, index, list) : value; 244 | computed < result.computed && (result = {value : value, computed : computed}); 245 | }); 246 | return result.value; 247 | }; 248 | 249 | // Shuffle an array. 250 | _.shuffle = function(obj) { 251 | var shuffled = [], rand; 252 | each(obj, function(value, index, list) { 253 | if (index == 0) { 254 | shuffled[0] = value; 255 | } else { 256 | rand = Math.floor(Math.random() * (index + 1)); 257 | shuffled[index] = shuffled[rand]; 258 | shuffled[rand] = value; 259 | } 260 | }); 261 | return shuffled; 262 | }; 263 | 264 | // Sort the object's values by a criterion produced by an iterator. 265 | _.sortBy = function(obj, iterator, context) { 266 | return _.pluck(_.map(obj, function(value, index, list) { 267 | return { 268 | value : value, 269 | criteria : iterator.call(context, value, index, list) 270 | }; 271 | }).sort(function(left, right) { 272 | var a = left.criteria, b = right.criteria; 273 | return a < b ? -1 : a > b ? 1 : 0; 274 | }), 'value'); 275 | }; 276 | 277 | // Groups the object's values by a criterion. Pass either a string attribute 278 | // to group by, or a function that returns the criterion. 279 | _.groupBy = function(obj, val) { 280 | var result = {}; 281 | var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; 282 | each(obj, function(value, index) { 283 | var key = iterator(value, index); 284 | (result[key] || (result[key] = [])).push(value); 285 | }); 286 | return result; 287 | }; 288 | 289 | // Use a comparator function to figure out at what index an object should 290 | // be inserted so as to maintain order. Uses binary search. 291 | _.sortedIndex = function(array, obj, iterator) { 292 | iterator || (iterator = _.identity); 293 | var low = 0, high = array.length; 294 | while (low < high) { 295 | var mid = (low + high) >> 1; 296 | iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid; 297 | } 298 | return low; 299 | }; 300 | 301 | // Safely convert anything iterable into a real, live array. 302 | _.toArray = function(iterable) { 303 | if (!iterable) return []; 304 | if (iterable.toArray) return iterable.toArray(); 305 | if (_.isArray(iterable)) return slice.call(iterable); 306 | if (_.isArguments(iterable)) return slice.call(iterable); 307 | return _.values(iterable); 308 | }; 309 | 310 | // Return the number of elements in an object. 311 | _.size = function(obj) { 312 | return _.toArray(obj).length; 313 | }; 314 | 315 | // Array Functions 316 | // --------------- 317 | 318 | // Get the first element of an array. Passing **n** will return the first N 319 | // values in the array. Aliased as `head`. The **guard** check allows it to work 320 | // with `_.map`. 321 | _.first = _.head = function(array, n, guard) { 322 | return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; 323 | }; 324 | 325 | // Returns everything but the last entry of the array. Especcialy useful on 326 | // the arguments object. Passing **n** will return all the values in 327 | // the array, excluding the last N. The **guard** check allows it to work with 328 | // `_.map`. 329 | _.initial = function(array, n, guard) { 330 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 331 | }; 332 | 333 | // Get the last element of an array. Passing **n** will return the last N 334 | // values in the array. The **guard** check allows it to work with `_.map`. 335 | _.last = function(array, n, guard) { 336 | if ((n != null) && !guard) { 337 | return slice.call(array, Math.max(array.length - n, 0)); 338 | } else { 339 | return array[array.length - 1]; 340 | } 341 | }; 342 | 343 | // Returns everything but the first entry of the array. Aliased as `tail`. 344 | // Especially useful on the arguments object. Passing an **index** will return 345 | // the rest of the values in the array from that index onward. The **guard** 346 | // check allows it to work with `_.map`. 347 | _.rest = _.tail = function(array, index, guard) { 348 | return slice.call(array, (index == null) || guard ? 1 : index); 349 | }; 350 | 351 | // Trim out all falsy values from an array. 352 | _.compact = function(array) { 353 | return _.filter(array, function(value){ return !!value; }); 354 | }; 355 | 356 | // Return a completely flattened version of an array. 357 | _.flatten = function(array, shallow) { 358 | return _.reduce(array, function(memo, value) { 359 | if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value)); 360 | memo[memo.length] = value; 361 | return memo; 362 | }, []); 363 | }; 364 | 365 | // Return a version of the array that does not contain the specified value(s). 366 | _.without = function(array) { 367 | return _.difference(array, slice.call(arguments, 1)); 368 | }; 369 | 370 | // Produce a duplicate-free version of the array. If the array has already 371 | // been sorted, you have the option of using a faster algorithm. 372 | // Aliased as `unique`. 373 | _.uniq = _.unique = function(array, isSorted, iterator) { 374 | var initial = iterator ? _.map(array, iterator) : array; 375 | var result = []; 376 | _.reduce(initial, function(memo, el, i) { 377 | if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) { 378 | memo[memo.length] = el; 379 | result[result.length] = array[i]; 380 | } 381 | return memo; 382 | }, []); 383 | return result; 384 | }; 385 | 386 | // Produce an array that contains the union: each distinct element from all of 387 | // the passed-in arrays. 388 | _.union = function() { 389 | return _.uniq(_.flatten(arguments, true)); 390 | }; 391 | 392 | // Produce an array that contains every item shared between all the 393 | // passed-in arrays. (Aliased as "intersect" for back-compat.) 394 | _.intersection = _.intersect = function(array) { 395 | var rest = slice.call(arguments, 1); 396 | return _.filter(_.uniq(array), function(item) { 397 | return _.every(rest, function(other) { 398 | return _.indexOf(other, item) >= 0; 399 | }); 400 | }); 401 | }; 402 | 403 | // Take the difference between one array and a number of other arrays. 404 | // Only the elements present in just the first array will remain. 405 | _.difference = function(array) { 406 | var rest = _.flatten(slice.call(arguments, 1)); 407 | return _.filter(array, function(value){ return !_.include(rest, value); }); 408 | }; 409 | 410 | // Zip together multiple lists into a single array -- elements that share 411 | // an index go together. 412 | _.zip = function() { 413 | var args = slice.call(arguments); 414 | var length = _.max(_.pluck(args, 'length')); 415 | var results = new Array(length); 416 | for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i); 417 | return results; 418 | }; 419 | 420 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 421 | // we need this function. Return the position of the first occurrence of an 422 | // item in an array, or -1 if the item is not included in the array. 423 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 424 | // If the array is large and already in sort order, pass `true` 425 | // for **isSorted** to use binary search. 426 | _.indexOf = function(array, item, isSorted) { 427 | if (array == null) return -1; 428 | var i, l; 429 | if (isSorted) { 430 | i = _.sortedIndex(array, item); 431 | return array[i] === item ? i : -1; 432 | } 433 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item); 434 | for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i; 435 | return -1; 436 | }; 437 | 438 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 439 | _.lastIndexOf = function(array, item) { 440 | if (array == null) return -1; 441 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item); 442 | var i = array.length; 443 | while (i--) if (i in array && array[i] === item) return i; 444 | return -1; 445 | }; 446 | 447 | // Generate an integer Array containing an arithmetic progression. A port of 448 | // the native Python `range()` function. See 449 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 450 | _.range = function(start, stop, step) { 451 | if (arguments.length <= 1) { 452 | stop = start || 0; 453 | start = 0; 454 | } 455 | step = arguments[2] || 1; 456 | 457 | var len = Math.max(Math.ceil((stop - start) / step), 0); 458 | var idx = 0; 459 | var range = new Array(len); 460 | 461 | while(idx < len) { 462 | range[idx++] = start; 463 | start += step; 464 | } 465 | 466 | return range; 467 | }; 468 | 469 | // Function (ahem) Functions 470 | // ------------------ 471 | 472 | // Reusable constructor function for prototype setting. 473 | var ctor = function(){}; 474 | 475 | // Create a function bound to a given object (assigning `this`, and arguments, 476 | // optionally). Binding with arguments is also known as `curry`. 477 | // Delegates to **ECMAScript 5**'s native `Function.bind` if available. 478 | // We check for `func.bind` first, to fail fast when `func` is undefined. 479 | _.bind = function bind(func, context) { 480 | var bound, args; 481 | if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 482 | if (!_.isFunction(func)) throw new TypeError; 483 | args = slice.call(arguments, 2); 484 | return bound = function() { 485 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 486 | ctor.prototype = func.prototype; 487 | var self = new ctor; 488 | var result = func.apply(self, args.concat(slice.call(arguments))); 489 | if (Object(result) === result) return result; 490 | return self; 491 | }; 492 | }; 493 | 494 | // Bind all of an object's methods to that object. Useful for ensuring that 495 | // all callbacks defined on an object belong to it. 496 | _.bindAll = function(obj) { 497 | var funcs = slice.call(arguments, 1); 498 | if (funcs.length == 0) funcs = _.functions(obj); 499 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 500 | return obj; 501 | }; 502 | 503 | // Memoize an expensive function by storing its results. 504 | _.memoize = function(func, hasher) { 505 | var memo = {}; 506 | hasher || (hasher = _.identity); 507 | return function() { 508 | var key = hasher.apply(this, arguments); 509 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 510 | }; 511 | }; 512 | 513 | // Delays a function for the given number of milliseconds, and then calls 514 | // it with the arguments supplied. 515 | _.delay = function(func, wait) { 516 | var args = slice.call(arguments, 2); 517 | return setTimeout(function(){ return func.apply(func, args); }, wait); 518 | }; 519 | 520 | // Defers a function, scheduling it to run after the current call stack has 521 | // cleared. 522 | _.defer = function(func) { 523 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 524 | }; 525 | 526 | // Returns a function, that, when invoked, will only be triggered at most once 527 | // during a given window of time. 528 | _.throttle = function(func, wait) { 529 | var context, args, timeout, throttling, more; 530 | var whenDone = _.debounce(function(){ more = throttling = false; }, wait); 531 | return function() { 532 | context = this; args = arguments; 533 | var later = function() { 534 | timeout = null; 535 | if (more) func.apply(context, args); 536 | whenDone(); 537 | }; 538 | if (!timeout) timeout = setTimeout(later, wait); 539 | if (throttling) { 540 | more = true; 541 | } else { 542 | func.apply(context, args); 543 | } 544 | whenDone(); 545 | throttling = true; 546 | }; 547 | }; 548 | 549 | // Returns a function, that, as long as it continues to be invoked, will not 550 | // be triggered. The function will be called after it stops being called for 551 | // N milliseconds. 552 | _.debounce = function(func, wait) { 553 | var timeout; 554 | return function() { 555 | var context = this, args = arguments; 556 | var later = function() { 557 | timeout = null; 558 | func.apply(context, args); 559 | }; 560 | clearTimeout(timeout); 561 | timeout = setTimeout(later, wait); 562 | }; 563 | }; 564 | 565 | // Returns a function that will be executed at most one time, no matter how 566 | // often you call it. Useful for lazy initialization. 567 | _.once = function(func) { 568 | var ran = false, memo; 569 | return function() { 570 | if (ran) return memo; 571 | ran = true; 572 | return memo = func.apply(this, arguments); 573 | }; 574 | }; 575 | 576 | // Returns the first function passed as an argument to the second, 577 | // allowing you to adjust arguments, run code before and after, and 578 | // conditionally execute the original function. 579 | _.wrap = function(func, wrapper) { 580 | return function() { 581 | var args = [func].concat(slice.call(arguments, 0)); 582 | return wrapper.apply(this, args); 583 | }; 584 | }; 585 | 586 | // Returns a function that is the composition of a list of functions, each 587 | // consuming the return value of the function that follows. 588 | _.compose = function() { 589 | var funcs = arguments; 590 | return function() { 591 | var args = arguments; 592 | for (var i = funcs.length - 1; i >= 0; i--) { 593 | args = [funcs[i].apply(this, args)]; 594 | } 595 | return args[0]; 596 | }; 597 | }; 598 | 599 | // Returns a function that will only be executed after being called N times. 600 | _.after = function(times, func) { 601 | if (times <= 0) return func(); 602 | return function() { 603 | if (--times < 1) { return func.apply(this, arguments); } 604 | }; 605 | }; 606 | 607 | // Object Functions 608 | // ---------------- 609 | 610 | // Retrieve the names of an object's properties. 611 | // Delegates to **ECMAScript 5**'s native `Object.keys` 612 | _.keys = nativeKeys || function(obj) { 613 | if (obj !== Object(obj)) throw new TypeError('Invalid object'); 614 | var keys = []; 615 | for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; 616 | return keys; 617 | }; 618 | 619 | // Retrieve the values of an object's properties. 620 | _.values = function(obj) { 621 | return _.map(obj, _.identity); 622 | }; 623 | 624 | // Return a sorted list of the function names available on the object. 625 | // Aliased as `methods` 626 | _.functions = _.methods = function(obj) { 627 | var names = []; 628 | for (var key in obj) { 629 | if (_.isFunction(obj[key])) names.push(key); 630 | } 631 | return names.sort(); 632 | }; 633 | 634 | // Extend a given object with all the properties in passed-in object(s). 635 | _.extend = function(obj) { 636 | each(slice.call(arguments, 1), function(source) { 637 | for (var prop in source) { 638 | obj[prop] = source[prop]; 639 | } 640 | }); 641 | return obj; 642 | }; 643 | 644 | // Fill in a given object with default properties. 645 | _.defaults = function(obj) { 646 | each(slice.call(arguments, 1), function(source) { 647 | for (var prop in source) { 648 | if (obj[prop] == null) obj[prop] = source[prop]; 649 | } 650 | }); 651 | return obj; 652 | }; 653 | 654 | // Create a (shallow-cloned) duplicate of an object. 655 | _.clone = function(obj) { 656 | if (!_.isObject(obj)) return obj; 657 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 658 | }; 659 | 660 | // Invokes interceptor with the obj, and then returns obj. 661 | // The primary purpose of this method is to "tap into" a method chain, in 662 | // order to perform operations on intermediate results within the chain. 663 | _.tap = function(obj, interceptor) { 664 | interceptor(obj); 665 | return obj; 666 | }; 667 | 668 | // Internal recursive comparison function. 669 | function eq(a, b, stack) { 670 | // Identical objects are equal. `0 === -0`, but they aren't identical. 671 | // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. 672 | if (a === b) return a !== 0 || 1 / a == 1 / b; 673 | // A strict comparison is necessary because `null == undefined`. 674 | if (a == null || b == null) return a === b; 675 | // Unwrap any wrapped objects. 676 | if (a._chain) a = a._wrapped; 677 | if (b._chain) b = b._wrapped; 678 | // Invoke a custom `isEqual` method if one is provided. 679 | if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b); 680 | if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a); 681 | // Compare `[[Class]]` names. 682 | var className = toString.call(a); 683 | if (className != toString.call(b)) return false; 684 | switch (className) { 685 | // Strings, numbers, dates, and booleans are compared by value. 686 | case '[object String]': 687 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 688 | // equivalent to `new String("5")`. 689 | return a == String(b); 690 | case '[object Number]': 691 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 692 | // other numeric values. 693 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 694 | case '[object Date]': 695 | case '[object Boolean]': 696 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 697 | // millisecond representations. Note that invalid dates with millisecond representations 698 | // of `NaN` are not equivalent. 699 | return +a == +b; 700 | // RegExps are compared by their source patterns and flags. 701 | case '[object RegExp]': 702 | return a.source == b.source && 703 | a.global == b.global && 704 | a.multiline == b.multiline && 705 | a.ignoreCase == b.ignoreCase; 706 | } 707 | if (typeof a != 'object' || typeof b != 'object') return false; 708 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 709 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 710 | var length = stack.length; 711 | while (length--) { 712 | // Linear search. Performance is inversely proportional to the number of 713 | // unique nested structures. 714 | if (stack[length] == a) return true; 715 | } 716 | // Add the first object to the stack of traversed objects. 717 | stack.push(a); 718 | var size = 0, result = true; 719 | // Recursively compare objects and arrays. 720 | if (className == '[object Array]') { 721 | // Compare array lengths to determine if a deep comparison is necessary. 722 | size = a.length; 723 | result = size == b.length; 724 | if (result) { 725 | // Deep compare the contents, ignoring non-numeric properties. 726 | while (size--) { 727 | // Ensure commutative equality for sparse arrays. 728 | if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break; 729 | } 730 | } 731 | } else { 732 | // Objects with different constructors are not equivalent. 733 | if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false; 734 | // Deep compare objects. 735 | for (var key in a) { 736 | if (_.has(a, key)) { 737 | // Count the expected number of properties. 738 | size++; 739 | // Deep compare each member. 740 | if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break; 741 | } 742 | } 743 | // Ensure that both objects contain the same number of properties. 744 | if (result) { 745 | for (key in b) { 746 | if (_.has(b, key) && !(size--)) break; 747 | } 748 | result = !size; 749 | } 750 | } 751 | // Remove the first object from the stack of traversed objects. 752 | stack.pop(); 753 | return result; 754 | } 755 | 756 | // Perform a deep comparison to check if two objects are equal. 757 | _.isEqual = function(a, b) { 758 | return eq(a, b, []); 759 | }; 760 | 761 | // Is a given array, string, or object empty? 762 | // An "empty" object has no enumerable own-properties. 763 | _.isEmpty = function(obj) { 764 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 765 | for (var key in obj) if (_.has(obj, key)) return false; 766 | return true; 767 | }; 768 | 769 | // Is a given value a DOM element? 770 | _.isElement = function(obj) { 771 | return !!(obj && obj.nodeType == 1); 772 | }; 773 | 774 | // Is a given value an array? 775 | // Delegates to ECMA5's native Array.isArray 776 | _.isArray = nativeIsArray || function(obj) { 777 | return toString.call(obj) == '[object Array]'; 778 | }; 779 | 780 | // Is a given variable an object? 781 | _.isObject = function(obj) { 782 | return obj === Object(obj); 783 | }; 784 | 785 | // Is a given variable an arguments object? 786 | _.isArguments = function(obj) { 787 | return toString.call(obj) == '[object Arguments]'; 788 | }; 789 | if (!_.isArguments(arguments)) { 790 | _.isArguments = function(obj) { 791 | return !!(obj && _.has(obj, 'callee')); 792 | }; 793 | } 794 | 795 | // Is a given value a function? 796 | _.isFunction = function(obj) { 797 | return toString.call(obj) == '[object Function]'; 798 | }; 799 | 800 | // Is a given value a string? 801 | _.isString = function(obj) { 802 | return toString.call(obj) == '[object String]'; 803 | }; 804 | 805 | // Is a given value a number? 806 | _.isNumber = function(obj) { 807 | return toString.call(obj) == '[object Number]'; 808 | }; 809 | 810 | // Is the given value `NaN`? 811 | _.isNaN = function(obj) { 812 | // `NaN` is the only value for which `===` is not reflexive. 813 | return obj !== obj; 814 | }; 815 | 816 | // Is a given value a boolean? 817 | _.isBoolean = function(obj) { 818 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 819 | }; 820 | 821 | // Is a given value a date? 822 | _.isDate = function(obj) { 823 | return toString.call(obj) == '[object Date]'; 824 | }; 825 | 826 | // Is the given value a regular expression? 827 | _.isRegExp = function(obj) { 828 | return toString.call(obj) == '[object RegExp]'; 829 | }; 830 | 831 | // Is a given value equal to null? 832 | _.isNull = function(obj) { 833 | return obj === null; 834 | }; 835 | 836 | // Is a given variable undefined? 837 | _.isUndefined = function(obj) { 838 | return obj === void 0; 839 | }; 840 | 841 | // Has own property? 842 | _.has = function(obj, key) { 843 | return hasOwnProperty.call(obj, key); 844 | }; 845 | 846 | // Utility Functions 847 | // ----------------- 848 | 849 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 850 | // previous owner. Returns a reference to the Underscore object. 851 | _.noConflict = function() { 852 | root._ = previousUnderscore; 853 | return this; 854 | }; 855 | 856 | // Keep the identity function around for default iterators. 857 | _.identity = function(value) { 858 | return value; 859 | }; 860 | 861 | // Run a function **n** times. 862 | _.times = function (n, iterator, context) { 863 | for (var i = 0; i < n; i++) iterator.call(context, i); 864 | }; 865 | 866 | // Escape a string for HTML interpolation. 867 | _.escape = function(string) { 868 | return (''+string).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/'); 869 | }; 870 | 871 | // Add your own custom functions to the Underscore object, ensuring that 872 | // they're correctly added to the OOP wrapper as well. 873 | _.mixin = function(obj) { 874 | each(_.functions(obj), function(name){ 875 | addToWrapper(name, _[name] = obj[name]); 876 | }); 877 | }; 878 | 879 | // Generate a unique integer id (unique within the entire client session). 880 | // Useful for temporary DOM ids. 881 | var idCounter = 0; 882 | _.uniqueId = function(prefix) { 883 | var id = idCounter++; 884 | return prefix ? prefix + id : id; 885 | }; 886 | 887 | // By default, Underscore uses ERB-style template delimiters, change the 888 | // following template settings to use alternative delimiters. 889 | _.templateSettings = { 890 | evaluate : /<%([\s\S]+?)%>/g, 891 | interpolate : /<%=([\s\S]+?)%>/g, 892 | escape : /<%-([\s\S]+?)%>/g 893 | }; 894 | 895 | // When customizing `templateSettings`, if you don't want to define an 896 | // interpolation, evaluation or escaping regex, we need one that is 897 | // guaranteed not to match. 898 | var noMatch = /.^/; 899 | 900 | // Within an interpolation, evaluation, or escaping, remove HTML escaping 901 | // that had been previously added. 902 | var unescape = function(code) { 903 | return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'"); 904 | }; 905 | 906 | // JavaScript micro-templating, similar to John Resig's implementation. 907 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 908 | // and correctly escapes quotes within interpolated code. 909 | _.template = function(str, data) { 910 | var c = _.templateSettings; 911 | var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' + 912 | 'with(obj||{}){__p.push(\'' + 913 | str.replace(/\\/g, '\\\\') 914 | .replace(/'/g, "\\'") 915 | .replace(c.escape || noMatch, function(match, code) { 916 | return "',_.escape(" + unescape(code) + "),'"; 917 | }) 918 | .replace(c.interpolate || noMatch, function(match, code) { 919 | return "'," + unescape(code) + ",'"; 920 | }) 921 | .replace(c.evaluate || noMatch, function(match, code) { 922 | return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('"; 923 | }) 924 | .replace(/\r/g, '\\r') 925 | .replace(/\n/g, '\\n') 926 | .replace(/\t/g, '\\t') 927 | + "');}return __p.join('');"; 928 | var func = new Function('obj', '_', tmpl); 929 | if (data) return func(data, _); 930 | return function(data) { 931 | return func.call(this, data, _); 932 | }; 933 | }; 934 | 935 | // Add a "chain" function, which will delegate to the wrapper. 936 | _.chain = function(obj) { 937 | return _(obj).chain(); 938 | }; 939 | 940 | // The OOP Wrapper 941 | // --------------- 942 | 943 | // If Underscore is called as a function, it returns a wrapped object that 944 | // can be used OO-style. This wrapper holds altered versions of all the 945 | // underscore functions. Wrapped objects may be chained. 946 | var wrapper = function(obj) { this._wrapped = obj; }; 947 | 948 | // Expose `wrapper.prototype` as `_.prototype` 949 | _.prototype = wrapper.prototype; 950 | 951 | // Helper function to continue chaining intermediate results. 952 | var result = function(obj, chain) { 953 | return chain ? _(obj).chain() : obj; 954 | }; 955 | 956 | // A method to easily add functions to the OOP wrapper. 957 | var addToWrapper = function(name, func) { 958 | wrapper.prototype[name] = function() { 959 | var args = slice.call(arguments); 960 | unshift.call(args, this._wrapped); 961 | return result(func.apply(_, args), this._chain); 962 | }; 963 | }; 964 | 965 | // Add all of the Underscore functions to the wrapper object. 966 | _.mixin(_); 967 | 968 | // Add all mutator Array functions to the wrapper. 969 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 970 | var method = ArrayProto[name]; 971 | wrapper.prototype[name] = function() { 972 | var wrapped = this._wrapped; 973 | method.apply(wrapped, arguments); 974 | var length = wrapped.length; 975 | if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0]; 976 | return result(wrapped, this._chain); 977 | }; 978 | }); 979 | 980 | // Add all accessor Array functions to the wrapper. 981 | each(['concat', 'join', 'slice'], function(name) { 982 | var method = ArrayProto[name]; 983 | wrapper.prototype[name] = function() { 984 | return result(method.apply(this._wrapped, arguments), this._chain); 985 | }; 986 | }); 987 | 988 | // Start chaining a wrapped Underscore object. 989 | wrapper.prototype.chain = function() { 990 | this._chain = true; 991 | return this; 992 | }; 993 | 994 | // Extracts the result from a wrapped and chained object. 995 | wrapper.prototype.value = function() { 996 | return this._wrapped; 997 | }; 998 | 999 | }).call(this); 1000 | -------------------------------------------------------------------------------- /examples/static/hiro/webui.css: -------------------------------------------------------------------------------- 1 | div.container { 2 | width: 700px; 3 | } 4 | 5 | h2 { 6 | margin: 75px 0 25px 0; 7 | } 8 | 9 | h2 img { 10 | margin-top: -13px; 11 | } 12 | 13 | div.runall { 14 | margin: 75px 0 25px 0; 15 | text-align: right; 16 | } 17 | 18 | table.suite { 19 | margin: 25px 0 0 0; 20 | } 21 | 22 | table.suite td.status { 23 | width: 100px; 24 | text-align: center; 25 | } 26 | 27 | table.suite tr.report td { 28 | background-color: #EEE; 29 | } 30 | 31 | table.suite tr.report td table td { 32 | border: none; 33 | } 34 | 35 | div.footer { 36 | text-align: center; 37 | margin-top: 100px; 38 | } 39 | 40 | /* Don't show fixture elements */ 41 | textarea { display: none; } 42 | -------------------------------------------------------------------------------- /examples/static/hiro/webui.js: -------------------------------------------------------------------------------- 1 | /*jshint browser:true, jquery:true, devel:true */ 2 | /*global _:false */ 3 | 4 | var hiro, main; 5 | 6 | (function () { 7 | "use strict"; 8 | 9 | var size = 0; 10 | var completed = 0; 11 | 12 | hiro = new Hiro(); 13 | 14 | main = function () { 15 | var qs = window.location.search.slice(1).split('.'); 16 | var req = qs.length ? qs[0] : null; 17 | 18 | _.each(hiro.suites, function (suite, name) { 19 | if (req && req !== name) 20 | return; 21 | 22 | var view = new SuiteView(name, suite); 23 | 24 | size += _.reduce(_.keys(suite.methods), function (memo, name) { 25 | return memo + (name.slice(0, 4) === "test" ? 1 : 0); 26 | }, 0); 27 | 28 | if (size === 0) 29 | return; 30 | 31 | view.render(); 32 | view.addListeners(); 33 | }); 34 | 35 | hiro.bind("hiro.onStart", function () { 36 | $("div.runall button").attr("disabled", true); 37 | }); 38 | 39 | hiro.bind("hiro.onComplete", function () { 40 | $("div.runall button").removeAttr("disabled"); 41 | }); 42 | 43 | // If we're inside the PhantomJS environment start running tests 44 | // right away. Otherwise assign a listener to the .runall button. 45 | 46 | if (window.haunted) 47 | return void hiro.run(); 48 | 49 | $("div.runall").click(function () { 50 | hiro.run(req || undefined); 51 | }); 52 | }; 53 | 54 | function SuiteView(name, model) { 55 | this.name = name; 56 | this.tests = []; 57 | 58 | this.templates = { 59 | suite: $("#template-suite").html(), 60 | report: $("#template-report").html(), 61 | error: $("#template-report-error").html() 62 | }; 63 | 64 | _.each(model.methods, _.bind(function (func, name) { 65 | if (name.slice(0, 4) !== "test") 66 | return; 67 | 68 | this.tests.push(name); 69 | }, this)); 70 | } 71 | 72 | SuiteView.prototype.render = function () { 73 | var states = [ "ready", "passed", "failed" ]; 74 | var html = _.template(this.templates.suite, { 75 | suiteName: this.name, 76 | tests: this.tests 77 | }); 78 | 79 | $("#suite-views").append(html); 80 | }; 81 | 82 | SuiteView.prototype.addListeners = function () { 83 | var self = this; 84 | 85 | hiro.bind("test.onStart", function (test) { 86 | var $el = $("#suite-" + self.name + " .test-" + test.name + " .status .label"); 87 | $el.html("RUNNING"); 88 | }); 89 | 90 | hiro.bind("test.onComplete", function (test, success, report) { 91 | var $el = $("#suite-" + self.name + " .test-" + test.name + " .status .label"); 92 | var $pr = $(".progress"); 93 | var $bar = $(".progress .bar"); 94 | 95 | if ($el.length === 0) 96 | return; 97 | 98 | completed += 1; 99 | $bar.css("width", ((completed / size) * 100) + "%"); 100 | 101 | if (success) 102 | return void $el.addClass("label-success").html("PASS"); 103 | 104 | $el.addClass("label-important").html("FAIL"); 105 | $pr.removeClass("progress-success").addClass("progress-danger"); 106 | 107 | var html; 108 | if (report.message) { 109 | html = _.template(self.templates.error, { 110 | message: report.message, 111 | source: report.source, 112 | location: report.location 113 | }); 114 | } else { 115 | html = _.template(self.templates.report, { 116 | assertion: report.name, 117 | expected: report.expected, 118 | actual: report.actual, 119 | location: report.location 120 | }); 121 | } 122 | 123 | $("#suite-" + self.name + " .report-" + test.name + " td") 124 | .html(html) 125 | .parent("tr") 126 | .show(); 127 | }); 128 | }; 129 | })(); 130 | -------------------------------------------------------------------------------- /examples/static/json2.js: -------------------------------------------------------------------------------- 1 | /* 2 | http://www.JSON.org/json2.js 3 | 2011-02-23 4 | 5 | Public Domain. 6 | 7 | NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. 8 | 9 | See http://www.JSON.org/js.html 10 | 11 | 12 | This code should be minified before deployment. 13 | See http://javascript.crockford.com/jsmin.html 14 | 15 | USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO 16 | NOT CONTROL. 17 | 18 | 19 | This file creates a global JSON object containing two methods: stringify 20 | and parse. 21 | 22 | JSON.stringify(value, replacer, space) 23 | value any JavaScript value, usually an object or array. 24 | 25 | replacer an optional parameter that determines how object 26 | values are stringified for objects. It can be a 27 | function or an array of strings. 28 | 29 | space an optional parameter that specifies the indentation 30 | of nested structures. If it is omitted, the text will 31 | be packed without extra whitespace. If it is a number, 32 | it will specify the number of spaces to indent at each 33 | level. If it is a string (such as '\t' or ' '), 34 | it contains the characters used to indent at each level. 35 | 36 | This method produces a JSON text from a JavaScript value. 37 | 38 | When an object value is found, if the object contains a toJSON 39 | method, its toJSON method will be called and the result will be 40 | stringified. A toJSON method does not serialize: it returns the 41 | value represented by the name/value pair that should be serialized, 42 | or undefined if nothing should be serialized. The toJSON method 43 | will be passed the key associated with the value, and this will be 44 | bound to the value 45 | 46 | For example, this would serialize Dates as ISO strings. 47 | 48 | Date.prototype.toJSON = function (key) { 49 | function f(n) { 50 | // Format integers to have at least two digits. 51 | return n < 10 ? '0' + n : n; 52 | } 53 | 54 | return this.getUTCFullYear() + '-' + 55 | f(this.getUTCMonth() + 1) + '-' + 56 | f(this.getUTCDate()) + 'T' + 57 | f(this.getUTCHours()) + ':' + 58 | f(this.getUTCMinutes()) + ':' + 59 | f(this.getUTCSeconds()) + 'Z'; 60 | }; 61 | 62 | You can provide an optional replacer method. It will be passed the 63 | key and value of each member, with this bound to the containing 64 | object. The value that is returned from your method will be 65 | serialized. If your method returns undefined, then the member will 66 | be excluded from the serialization. 67 | 68 | If the replacer parameter is an array of strings, then it will be 69 | used to select the members to be serialized. It filters the results 70 | such that only members with keys listed in the replacer array are 71 | stringified. 72 | 73 | Values that do not have JSON representations, such as undefined or 74 | functions, will not be serialized. Such values in objects will be 75 | dropped; in arrays they will be replaced with null. You can use 76 | a replacer function to replace those with JSON values. 77 | JSON.stringify(undefined) returns undefined. 78 | 79 | The optional space parameter produces a stringification of the 80 | value that is filled with line breaks and indentation to make it 81 | easier to read. 82 | 83 | If the space parameter is a non-empty string, then that string will 84 | be used for indentation. If the space parameter is a number, then 85 | the indentation will be that many spaces. 86 | 87 | Example: 88 | 89 | text = JSON.stringify(['e', {pluribus: 'unum'}]); 90 | // text is '["e",{"pluribus":"unum"}]' 91 | 92 | 93 | text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); 94 | // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' 95 | 96 | text = JSON.stringify([new Date()], function (key, value) { 97 | return this[key] instanceof Date ? 98 | 'Date(' + this[key] + ')' : value; 99 | }); 100 | // text is '["Date(---current time---)"]' 101 | 102 | 103 | JSON.parse(text, reviver) 104 | This method parses a JSON text to produce an object or array. 105 | It can throw a SyntaxError exception. 106 | 107 | The optional reviver parameter is a function that can filter and 108 | transform the results. It receives each of the keys and values, 109 | and its return value is used instead of the original value. 110 | If it returns what it received, then the structure is not modified. 111 | If it returns undefined then the member is deleted. 112 | 113 | Example: 114 | 115 | // Parse the text. Values that look like ISO date strings will 116 | // be converted to Date objects. 117 | 118 | myData = JSON.parse(text, function (key, value) { 119 | var a; 120 | if (typeof value === 'string') { 121 | a = 122 | /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); 123 | if (a) { 124 | return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], 125 | +a[5], +a[6])); 126 | } 127 | } 128 | return value; 129 | }); 130 | 131 | myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { 132 | var d; 133 | if (typeof value === 'string' && 134 | value.slice(0, 5) === 'Date(' && 135 | value.slice(-1) === ')') { 136 | d = new Date(value.slice(5, -1)); 137 | if (d) { 138 | return d; 139 | } 140 | } 141 | return value; 142 | }); 143 | 144 | 145 | This is a reference implementation. You are free to copy, modify, or 146 | redistribute. 147 | */ 148 | 149 | /*jslint evil: true, strict: false, regexp: false */ 150 | 151 | /*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, 152 | call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, 153 | getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, 154 | lastIndex, length, parse, prototype, push, replace, slice, stringify, 155 | test, toJSON, toString, valueOf 156 | */ 157 | 158 | 159 | // Create a JSON object only if one does not already exist. We create the 160 | // methods in a closure to avoid creating global variables. 161 | 162 | var JSON; 163 | if (!JSON) { 164 | JSON = {}; 165 | } 166 | 167 | (function () { 168 | "use strict"; 169 | 170 | function f(n) { 171 | // Format integers to have at least two digits. 172 | return n < 10 ? '0' + n : n; 173 | } 174 | 175 | if (typeof Date.prototype.toJSON !== 'function') { 176 | 177 | Date.prototype.toJSON = function (key) { 178 | 179 | return isFinite(this.valueOf()) ? 180 | this.getUTCFullYear() + '-' + 181 | f(this.getUTCMonth() + 1) + '-' + 182 | f(this.getUTCDate()) + 'T' + 183 | f(this.getUTCHours()) + ':' + 184 | f(this.getUTCMinutes()) + ':' + 185 | f(this.getUTCSeconds()) + 'Z' : null; 186 | }; 187 | 188 | String.prototype.toJSON = 189 | Number.prototype.toJSON = 190 | Boolean.prototype.toJSON = function (key) { 191 | return this.valueOf(); 192 | }; 193 | } 194 | 195 | var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 196 | escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, 197 | gap, 198 | indent, 199 | meta = { // table of character substitutions 200 | '\b': '\\b', 201 | '\t': '\\t', 202 | '\n': '\\n', 203 | '\f': '\\f', 204 | '\r': '\\r', 205 | '"' : '\\"', 206 | '\\': '\\\\' 207 | }, 208 | rep; 209 | 210 | 211 | function quote(string) { 212 | 213 | // If the string contains no control characters, no quote characters, and no 214 | // backslash characters, then we can safely slap some quotes around it. 215 | // Otherwise we must also replace the offending characters with safe escape 216 | // sequences. 217 | 218 | escapable.lastIndex = 0; 219 | return escapable.test(string) ? '"' + string.replace(escapable, function (a) { 220 | var c = meta[a]; 221 | return typeof c === 'string' ? c : 222 | '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 223 | }) + '"' : '"' + string + '"'; 224 | } 225 | 226 | 227 | function str(key, holder) { 228 | 229 | // Produce a string from holder[key]. 230 | 231 | var i, // The loop counter. 232 | k, // The member key. 233 | v, // The member value. 234 | length, 235 | mind = gap, 236 | partial, 237 | value = holder[key]; 238 | 239 | // If the value has a toJSON method, call it to obtain a replacement value. 240 | 241 | if (value && typeof value === 'object' && 242 | typeof value.toJSON === 'function') { 243 | value = value.toJSON(key); 244 | } 245 | 246 | // If we were called with a replacer function, then call the replacer to 247 | // obtain a replacement value. 248 | 249 | if (typeof rep === 'function') { 250 | value = rep.call(holder, key, value); 251 | } 252 | 253 | // What happens next depends on the value's type. 254 | 255 | switch (typeof value) { 256 | case 'string': 257 | return quote(value); 258 | 259 | case 'number': 260 | 261 | // JSON numbers must be finite. Encode non-finite numbers as null. 262 | 263 | return isFinite(value) ? String(value) : 'null'; 264 | 265 | case 'boolean': 266 | case 'null': 267 | 268 | // If the value is a boolean or null, convert it to a string. Note: 269 | // typeof null does not produce 'null'. The case is included here in 270 | // the remote chance that this gets fixed someday. 271 | 272 | return String(value); 273 | 274 | // If the type is 'object', we might be dealing with an object or an array or 275 | // null. 276 | 277 | case 'object': 278 | 279 | // Due to a specification blunder in ECMAScript, typeof null is 'object', 280 | // so watch out for that case. 281 | 282 | if (!value) { 283 | return 'null'; 284 | } 285 | 286 | // Make an array to hold the partial results of stringifying this object value. 287 | 288 | gap += indent; 289 | partial = []; 290 | 291 | // Is the value an array? 292 | 293 | if (Object.prototype.toString.apply(value) === '[object Array]') { 294 | 295 | // The value is an array. Stringify every element. Use null as a placeholder 296 | // for non-JSON values. 297 | 298 | length = value.length; 299 | for (i = 0; i < length; i += 1) { 300 | partial[i] = str(i, value) || 'null'; 301 | } 302 | 303 | // Join all of the elements together, separated with commas, and wrap them in 304 | // brackets. 305 | 306 | v = partial.length === 0 ? '[]' : gap ? 307 | '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 308 | '[' + partial.join(',') + ']'; 309 | gap = mind; 310 | return v; 311 | } 312 | 313 | // If the replacer is an array, use it to select the members to be stringified. 314 | 315 | if (rep && typeof rep === 'object') { 316 | length = rep.length; 317 | for (i = 0; i < length; i += 1) { 318 | if (typeof rep[i] === 'string') { 319 | k = rep[i]; 320 | v = str(k, value); 321 | if (v) { 322 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 323 | } 324 | } 325 | } 326 | } else { 327 | 328 | // Otherwise, iterate through all of the keys in the object. 329 | 330 | for (k in value) { 331 | if (Object.prototype.hasOwnProperty.call(value, k)) { 332 | v = str(k, value); 333 | if (v) { 334 | partial.push(quote(k) + (gap ? ': ' : ':') + v); 335 | } 336 | } 337 | } 338 | } 339 | 340 | // Join all of the member texts together, separated with commas, 341 | // and wrap them in braces. 342 | 343 | v = partial.length === 0 ? '{}' : gap ? 344 | '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : 345 | '{' + partial.join(',') + '}'; 346 | gap = mind; 347 | return v; 348 | } 349 | } 350 | 351 | // If the JSON object does not yet have a stringify method, give it one. 352 | 353 | if (typeof JSON.stringify !== 'function') { 354 | JSON.stringify = function (value, replacer, space) { 355 | 356 | // The stringify method takes a value and an optional replacer, and an optional 357 | // space parameter, and returns a JSON text. The replacer can be a function 358 | // that can replace values, or an array of strings that will select the keys. 359 | // A default replacer method can be provided. Use of the space parameter can 360 | // produce text that is more easily readable. 361 | 362 | var i; 363 | gap = ''; 364 | indent = ''; 365 | 366 | // If the space parameter is a number, make an indent string containing that 367 | // many spaces. 368 | 369 | if (typeof space === 'number') { 370 | for (i = 0; i < space; i += 1) { 371 | indent += ' '; 372 | } 373 | 374 | // If the space parameter is a string, it will be used as the indent string. 375 | 376 | } else if (typeof space === 'string') { 377 | indent = space; 378 | } 379 | 380 | // If there is a replacer, it must be a function or an array. 381 | // Otherwise, throw an error. 382 | 383 | rep = replacer; 384 | if (replacer && typeof replacer !== 'function' && 385 | (typeof replacer !== 'object' || 386 | typeof replacer.length !== 'number')) { 387 | throw new Error('JSON.stringify'); 388 | } 389 | 390 | // Make a fake root object containing our value under the key of ''. 391 | // Return the result of stringifying the value. 392 | 393 | return str('', {'': value}); 394 | }; 395 | } 396 | 397 | 398 | // If the JSON object does not yet have a parse method, give it one. 399 | 400 | if (typeof JSON.parse !== 'function') { 401 | JSON.parse = function (text, reviver) { 402 | 403 | // The parse method takes a text and an optional reviver function, and returns 404 | // a JavaScript value if the text is a valid JSON text. 405 | 406 | var j; 407 | 408 | function walk(holder, key) { 409 | 410 | // The walk method is used to recursively walk the resulting structure so 411 | // that modifications can be made. 412 | 413 | var k, v, value = holder[key]; 414 | if (value && typeof value === 'object') { 415 | for (k in value) { 416 | if (Object.prototype.hasOwnProperty.call(value, k)) { 417 | v = walk(value, k); 418 | if (v !== undefined) { 419 | value[k] = v; 420 | } else { 421 | delete value[k]; 422 | } 423 | } 424 | } 425 | } 426 | return reviver.call(holder, key, value); 427 | } 428 | 429 | 430 | // Parsing happens in four stages. In the first stage, we replace certain 431 | // Unicode characters with escape sequences. JavaScript handles many characters 432 | // incorrectly, either silently deleting them, or treating them as line endings. 433 | 434 | text = String(text); 435 | cx.lastIndex = 0; 436 | if (cx.test(text)) { 437 | text = text.replace(cx, function (a) { 438 | return '\\u' + 439 | ('0000' + a.charCodeAt(0).toString(16)).slice(-4); 440 | }); 441 | } 442 | 443 | // In the second stage, we run the text against regular expressions that look 444 | // for non-JSON patterns. We are especially concerned with '()' and 'new' 445 | // because they can cause invocation, and '=' because it can cause mutation. 446 | // But just to be safe, we want to reject all unexpected forms. 447 | 448 | // We split the second stage into 4 regexp operations in order to work around 449 | // crippling inefficiencies in IE's and Safari's regexp engines. First we 450 | // replace the JSON backslash pairs with '@' (a non-JSON character). Second, we 451 | // replace all simple value tokens with ']' characters. Third, we delete all 452 | // open brackets that follow a colon or comma or that begin the text. Finally, 453 | // we look to see that the remaining characters are only whitespace or ']' or 454 | // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. 455 | 456 | if (/^[\],:{}\s]*$/ 457 | .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') 458 | .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') 459 | .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { 460 | 461 | // In the third stage we use the eval function to compile the text into a 462 | // JavaScript structure. The '{' operator is subject to a syntactic ambiguity 463 | // in JavaScript: it can begin a block or an object literal. We wrap the text 464 | // in parens to eliminate the ambiguity. 465 | 466 | j = eval('(' + text + ')'); 467 | 468 | // In the optional fourth stage, we recursively walk the new structure, passing 469 | // each name/value pair to a reviver function for possible transformation. 470 | 471 | return typeof reviver === 'function' ? 472 | walk({'': j}, '') : j; 473 | } 474 | 475 | // If the text is not JSON parseable, then a SyntaxError is thrown. 476 | 477 | throw new SyntaxError('JSON.parse'); 478 | }; 479 | } 480 | }()); -------------------------------------------------------------------------------- /examples/static/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/loading.gif -------------------------------------------------------------------------------- /examples/static/partly_cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benvinegar/thirdpartyjs-code/52afdb25f9f72ff3f59c0bf1273b4f6ccc699cc4/examples/static/partly_cloudy.png -------------------------------------------------------------------------------- /examples/static/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.7.0pre - A JavaScript Unit Testing Framework 3 | * 4 | * http://docs.jquery.com/QUnit 5 | * 6 | * Copyright (c) 2012 John Resig, Jörn Zaefferer 7 | * Dual licensed under the MIT (MIT-LICENSE.txt) 8 | * or GPL (GPL-LICENSE.txt) licenses. 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 15px 15px 0 0; 42 | -moz-border-radius: 15px 15px 0 0; 43 | -webkit-border-top-right-radius: 15px; 44 | -webkit-border-top-left-radius: 15px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-header label { 58 | display: inline-block; 59 | padding-left: 0.5em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | } 71 | 72 | #qunit-userAgent { 73 | padding: 0.5em 0 0.5em 2.5em; 74 | background-color: #2b81af; 75 | color: #fff; 76 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 77 | } 78 | 79 | 80 | /** Tests: Pass/Fail */ 81 | 82 | #qunit-tests { 83 | list-style-position: inside; 84 | } 85 | 86 | #qunit-tests li { 87 | padding: 0.4em 0.5em 0.4em 2.5em; 88 | border-bottom: 1px solid #fff; 89 | list-style-position: inside; 90 | } 91 | 92 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 93 | display: none; 94 | } 95 | 96 | #qunit-tests li strong { 97 | cursor: pointer; 98 | } 99 | 100 | #qunit-tests li a { 101 | padding: 0.5em; 102 | color: #c2ccd1; 103 | text-decoration: none; 104 | } 105 | #qunit-tests li a:hover, 106 | #qunit-tests li a:focus { 107 | color: #000; 108 | } 109 | 110 | #qunit-tests ol { 111 | margin-top: 0.5em; 112 | padding: 0.5em; 113 | 114 | background-color: #fff; 115 | 116 | border-radius: 15px; 117 | -moz-border-radius: 15px; 118 | -webkit-border-radius: 15px; 119 | 120 | box-shadow: inset 0px 2px 13px #999; 121 | -moz-box-shadow: inset 0px 2px 13px #999; 122 | -webkit-box-shadow: inset 0px 2px 13px #999; 123 | } 124 | 125 | #qunit-tests table { 126 | border-collapse: collapse; 127 | margin-top: .2em; 128 | } 129 | 130 | #qunit-tests th { 131 | text-align: right; 132 | vertical-align: top; 133 | padding: 0 .5em 0 0; 134 | } 135 | 136 | #qunit-tests td { 137 | vertical-align: top; 138 | } 139 | 140 | #qunit-tests pre { 141 | margin: 0; 142 | white-space: pre-wrap; 143 | word-wrap: break-word; 144 | } 145 | 146 | #qunit-tests del { 147 | background-color: #e0f2be; 148 | color: #374e0c; 149 | text-decoration: none; 150 | } 151 | 152 | #qunit-tests ins { 153 | background-color: #ffcaca; 154 | color: #500; 155 | text-decoration: none; 156 | } 157 | 158 | /*** Test Counts */ 159 | 160 | #qunit-tests b.counts { color: black; } 161 | #qunit-tests b.passed { color: #5E740B; } 162 | #qunit-tests b.failed { color: #710909; } 163 | 164 | #qunit-tests li li { 165 | margin: 0.5em; 166 | padding: 0.4em 0.5em 0.4em 0.5em; 167 | background-color: #fff; 168 | border-bottom: none; 169 | list-style-position: inside; 170 | } 171 | 172 | /*** Passing Styles */ 173 | 174 | #qunit-tests li li.pass { 175 | color: #5E740B; 176 | background-color: #fff; 177 | border-left: 26px solid #C6E746; 178 | } 179 | 180 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 181 | #qunit-tests .pass .test-name { color: #366097; } 182 | 183 | #qunit-tests .pass .test-actual, 184 | #qunit-tests .pass .test-expected { color: #999999; } 185 | 186 | #qunit-banner.qunit-pass { background-color: #C6E746; } 187 | 188 | /*** Failing Styles */ 189 | 190 | #qunit-tests li li.fail { 191 | color: #710909; 192 | background-color: #fff; 193 | border-left: 26px solid #EE5757; 194 | white-space: pre; 195 | } 196 | 197 | #qunit-tests > li:last-child { 198 | border-radius: 0 0 15px 15px; 199 | -moz-border-radius: 0 0 15px 15px; 200 | -webkit-border-bottom-right-radius: 15px; 201 | -webkit-border-bottom-left-radius: 15px; 202 | } 203 | 204 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 205 | #qunit-tests .fail .test-name, 206 | #qunit-tests .fail .module-name { color: #000000; } 207 | 208 | #qunit-tests .fail .test-actual { color: #EE5757; } 209 | #qunit-tests .fail .test-expected { color: green; } 210 | 211 | #qunit-banner.qunit-fail { background-color: #EE5757; } 212 | 213 | 214 | /** Result */ 215 | 216 | #qunit-testresult { 217 | padding: 0.5em 0.5em 0.5em 2.5em; 218 | 219 | color: #2b81af; 220 | background-color: #D2E0E6; 221 | 222 | border-bottom: 1px solid white; 223 | } 224 | #qunit-testresult .module-name { 225 | font-weight: bold; 226 | } 227 | 228 | /** Fixture */ 229 | 230 | #qunit-fixture { 231 | position: absolute; 232 | top: -10000px; 233 | left: -10000px; 234 | width: 1000px; 235 | height: 1000px; 236 | } 237 | -------------------------------------------------------------------------------- /examples/static/sdk.js: -------------------------------------------------------------------------------- 1 | (function(window, undefined) { 2 | var Stork = {}; 3 | 4 | var listeners = {}; 5 | 6 | Stork.listen = function(eventName, handler) { 7 | if (typeof listeners[eventName] === 'undefined') 8 | listeners[eventName] = []; 9 | 10 | listeners[eventName].push(handler); 11 | }; 12 | 13 | Stork.unlisten = function(eventName, handler) { 14 | if (!listeners[eventName]) 15 | return; 16 | 17 | for (var i = 0; i < listeners[eventName].length; i++) { 18 | if (listeners[eventName][i] === handler) { 19 | listeners.splice(i, 1); 20 | break; 21 | } 22 | } 23 | }; 24 | 25 | Stork.broadcast = function(eventName) { 26 | if (!listeners[eventName]) 27 | return; 28 | 29 | for (var i = 0; i < listeners[eventName].length; i++) { 30 | listeners[eventName][i](); 31 | } 32 | }; 33 | 34 | window.Stork = Stork; 35 | })(this); -------------------------------------------------------------------------------- /examples/templates/01/json-and-prototypejs/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

This example demonstrates how some versions of the Prototype.js library can cause incompatible code changes, even on native/default objects like JSON.stringify.

10 | 11 |

On an unmodified page

12 | 13 |

JSON.stringify([1, 2, undefined]) produces the following output: 14 | 15 | 18 | 19 |

After loading Protoype.js

20 | 21 | 22 |

JSON.stringify([1, 2, undefined]) produces the following output:

23 | 24 | 27 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/01/weather-widget/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

10 | This example from Chapter 1 renders a weather widget on the publisher's 11 | web page using a standard script include. 12 |

13 | 14 | 15 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/02/loading-files/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

This example loads two files, lib.js and styles.css, dynamically via JavaScript.

10 | 11 |

When styles.css is loaded, this sentence will appear blue.

12 | 13 | 97 | 98 | 99 |

Tested in Chrome 13, Safari 5, Firefox 5, IE 7+, Opera 11.

100 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/02/loading-files/lib.js: -------------------------------------------------------------------------------- 1 | log('… File executed'); -------------------------------------------------------------------------------- /examples/templates/02/loading-files/styles.css: -------------------------------------------------------------------------------- 1 | #css-ready { 2 | color: #797979; 3 | } 4 | 5 | #css-ready-sentence { 6 | color: #0000ff; 7 | } -------------------------------------------------------------------------------- /examples/templates/02/passing-args/data-attr.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function getProductId() { 3 | var scripts = document.getElementsByTagName('script'), 4 | id; 5 | 6 | for (var i = 0; i < scripts.length; i++) { 7 | id = scripts[i].getAttribute('data-stork-productId'); 8 | if (id) 9 | return id; 10 | } 11 | return null; 12 | } 13 | var params = { 14 | 'productId': getProductId() 15 | }; 16 | document.getElementById('data-attr-out').innerHTML = JSON.stringify(params); 17 | })(); -------------------------------------------------------------------------------- /examples/templates/02/passing-args/fragment.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | function getScriptUrl(re) { 3 | var scripts = document.getElementsByTagName('script'), 4 | element, 5 | src; 6 | 7 | for (var i = 0; i < scripts.length; i++) { 8 | element = scripts[i]; 9 | 10 | src = element.getAttribute ? 11 | element.getAttribute('src') : el.src; 12 | 13 | if (src && re.test(src)) { 14 | return src; 15 | } 16 | } 17 | return null; 18 | } 19 | 20 | function getQueryParameters(query) { 21 | 22 | var args = query.split('&'), 23 | params = {}, 24 | pair, 25 | key, 26 | value; 27 | 28 | function decode(string) { 29 | return decodeURIComponent(string || "") 30 | .replace('+', ' '); 31 | } 32 | 33 | for (var i = 0; i < args.length; i++) { 34 | pair = args[i].split('='); 35 | key = decode(pair.shift()); 36 | value = decode(pair ? pair[0] : null); 37 | 38 | params[key] = value; 39 | } 40 | return params; 41 | }; 42 | 43 | var url = getScriptUrl(/\/fragment\.js/); 44 | 45 | var params = getQueryParameters(url.replace(/^.*\#/, '')); 46 | 47 | document.getElementById('fragment-ident-out').innerHTML = JSON.stringify(params); 48 | })(); -------------------------------------------------------------------------------- /examples/templates/02/passing-args/globals.js: -------------------------------------------------------------------------------- 1 | document.getElementById('global-var-out').innerHTML = JSON.stringify(Stork); -------------------------------------------------------------------------------- /examples/templates/02/passing-args/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 | 11 | 14 | 15 |

This example demonstrates a third-party script capturing publisher-defined parameters. 16 | Four techniques are used: using the query string, fragment identifier (#), HTML5 data-* attributes, and global variable arrays.

17 | 18 |

Query string

19 | 20 |

21 | querystring.js?productId=1337 22 |

23 | 24 |

 25 | 
 26 |     
 38 | 
 39 | 
 40 |     

Fragment identifier (#)

41 | 42 |

43 | fragment.js#productId=1337 44 |

45 | 46 |

 47 | 
 48 |     
 60 | 
 61 |     

data-* attributes

62 | 63 |

64 | data-stork-productId="1337" 65 |

66 | 67 | 68 |

 69 | 
 70 |     
 82 | 
 83 |     

Global variable arrays

84 | 85 |

86 | Stork.productId = Stork.productId || [];
87 | Stork.productId.push(1337); 88 |

89 | 90 |

 91 | 
 92 | 
 93 |     
110 | {% endblock %}


--------------------------------------------------------------------------------
/examples/templates/02/passing-args/querystring.js:
--------------------------------------------------------------------------------
 1 | (function() {
 2 | 	function getScriptUrl(re) {
 3 | 	    var scripts = document.getElementsByTagName('script'),
 4 | 	        element,
 5 | 	        src;
 6 | 
 7 | 	    for (var i = 0; i < scripts.length; i++) {
 8 | 	        element = scripts[i];
 9 | 	    
10 | 	        src = element.getAttribute ? 
11 | 	            element.getAttribute('src') : el.src;
12 | 
13 | 	        if (src && re.test(src)) {
14 | 	            return src;
15 | 	        }
16 | 	    }	
17 | 	    return null;
18 | 	}
19 | 
20 | 	function getQueryParameters(query) {
21 | 
22 | 	    var args   = query.split('&'),
23 | 	        params = {},
24 | 	        pair, 
25 | 	        key, 
26 | 	        value;
27 | 
28 | 	    function decode(string) {
29 | 	        return decodeURIComponent(string || "")
30 | 	            .replace('+', ' ');
31 | 	    }
32 | 	    
33 | 	    for (var i = 0; i < args.length; i++) {
34 | 	        pair  = args[i].split('=');
35 | 	        key   = decode(pair.shift());
36 | 	        value = decode(pair ? pair[0] : null);
37 | 	        
38 | 	        params[key] = value;
39 | 	    }
40 | 	    return params;
41 | 	};
42 | 
43 | 	var url  = getScriptUrl(/\/querystring\.js/);
44 | 	var params = getQueryParameters(url.replace(/^.*\?/, ''));
45 | 
46 | 	document.getElementById('query-string-out').innerHTML = JSON.stringify(params);
47 | 
48 | })();


--------------------------------------------------------------------------------
/examples/templates/02/script-blocking/index.html:
--------------------------------------------------------------------------------
 1 | {% extends 'example.html' %}
 2 | 
 3 | {% block content %}
 4 |     
 7 |     
 8 |     

Blocking example

9 |

Below this line of text is a standard <script> include that loads a slow-loading JavaScript file. Below that script include is further page content, but it will not appear until the script has finished loading (3 seconds).

10 | 11 | 12 | 13 | 14 |

This part of the document won't render until the script is loaded.

15 | 16 |
17 | 18 |

Async example

19 |

Now we load the same slow-loading file asynchronously by dynamically creating a <script> element and appending it to the DOM. 20 | 21 |

22 | 38 | 39 |

This content appears even while the script is loading.

40 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/02/script-blocking/removeSpinner.js: -------------------------------------------------------------------------------- 1 | var img = document.getElementsByTagName('img')[0];img.parentNode.removeChild(img); -------------------------------------------------------------------------------- /examples/templates/03/rendering-html/append.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var html = '\ 3 |

\ 4 |

Mikon E90 Digital SLR

\ 5 | \ 6 |

$599.99

\ 7 |

4.3/5.0 • 176 Reviews

\ 8 |
'; 9 | 10 | var div = document.createElement('div'); 11 | div.innerHTML = html; 12 | 13 | var appendTo = document.getElementById('stork-widget'); 14 | appendTo.parentNode.insertBefore(div, appendTo); 15 | })(); 16 | -------------------------------------------------------------------------------- /examples/templates/03/rendering-html/docwrite.js: -------------------------------------------------------------------------------- 1 | document.write('\ 2 |
\ 3 |

Mikon E90 Digital SLR

\ 4 | \ 5 |

$599.99

\ 6 |

4.3/5.0 • 176 Reviews

\ 7 |
'); -------------------------------------------------------------------------------- /examples/templates/03/rendering-html/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 7 | 8 |

document.write

9 | 10 | 11 | 12 |

Appended

13 | 24 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/03/rendering-iframes/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Mikon E90 Digital SLR

6 | 7 |

$599.99

8 |

4.3/5.0 • 176 Reviews

9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/templates/03/rendering-iframes/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 7 | 8 |

src-less iframe

9 | 10 | 27 | 28 |

External iframe

29 | 30 | 40 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/03/rendering-multiple/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block head %} 4 | {{ super() }} 5 | 6 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 | 13 | 14 |

This example includes multiple copies of the script include snippet. Each of these renders its own widget.

15 | 16 |

First

17 | 28 | 29 |

Second

30 | 41 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/03/rendering-multiple/widget.js: -------------------------------------------------------------------------------- 1 | if (!window.Stork) { 2 | Stork = {}; 3 | } 4 | 5 | (function(Stork, window, undefined) { 6 | 7 | if (typeof Stork.widgetCount === 'undefined') { 8 | Stork.widgetCount = 0; 9 | } 10 | 11 | function append(html) { 12 | $('[data-stork-widget]').each(function(i, elem) { 13 | if (Stork.widgetCount === i) { 14 | $(elem).before(html); 15 | Stork.widgetCount++; 16 | return; 17 | } 18 | }); 19 | } 20 | 21 | append('
Widget HTML
'); 22 | })(Stork, window); 23 | -------------------------------------------------------------------------------- /examples/templates/04/cors/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

Simple CORS Request

10 |

11 | Server responded with: 12 | 13 |

    14 | 15 |
16 |

17 | 18 |

Browser support for CORS

19 | 20 | 53 | 54 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/04/jsonp/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 6 | 9 | 10 |

JSONP Response:

11 | 12 |
13 | 14 | 27 | 28 | 29 |

Tested in Chrome 13, Safari 5, Firefox 5, IE 7+, Opera 11.

30 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/04/subdomain-proxy/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block header %} 4 | 7 | {% endblock %} 8 | 9 | {% block content %} 10 | 11 | 14 | 15 |

This example uses an iframe proxy to communicate with an API endpoint using AJAX.

16 | 17 |

Response:

18 | 19 |
20 | 21 | 51 | 52 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/04/subdomain-proxy/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1337, 4 | "name": "Fuji SmoothPix 3000", 5 | "megapixels": 12, 6 | "price": 299 7 | } 8 | ] -------------------------------------------------------------------------------- /examples/templates/04/subdomain-proxy/proxy.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /examples/templates/05/hash-transport/client.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 7 | 8 |

This example communicates with an iframe hosted on a remote domain using the browser url's document fragment (hash). 9 | 10 | 44 | 45 |

46 | 47 |
48 | 49 |

50 |

51 |

52 | 53 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/05/hash-transport/server.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/templates/05/name-transport/client.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 7 | 8 |

This example communicates with an iframe hosted on a remote domain using the window.name property. 9 | 10 | 42 | 43 |

44 | 45 |
46 | 47 |

48 |

49 |

50 | 51 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/05/name-transport/empty.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/templates/05/name-transport/server.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/templates/05/postmessage/client.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 7 | 8 |

This example communicates with an iframe hosted on a remote domain using the window.postMessage HTML5 API. 9 | 10 | 36 | 37 |

38 | 39 |
40 | 41 |

42 |

43 |

44 | 45 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/05/postmessage/server.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/templates/06/auth-new-window/auth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sign in to Camera Stork 5 | 6 | 11 | 12 | 13 |
14 |
15 | Sign in 16 |
17 | 18 |
19 |
20 | 21 |
22 | 23 |
24 |
25 |
26 |
27 | 28 | or Cancel 29 |
30 |
31 | 32 | -------------------------------------------------------------------------------- /examples/templates/06/auth-new-window/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Setting Cookies in a New Window 5 | 6 | 7 | Login 8 | 9 |
10 | 11 | 49 | 50 | -------------------------------------------------------------------------------- /examples/templates/06/auth-new-window/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

10 | This page is designed to simulate a publisher's website. The iframe hosts 11 | your third-party application. With third-party cookies disabled, the iframe 12 | would be incapable of setting cookies to authenticate users. But by opening 13 | a new window, cookies can be set therein. 14 |

15 | 16 | 17 | 18 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/06/auth-new-window/success.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /examples/templates/06/auth-new-window/widget.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var insertAfter = document.getElementsByTagName('script')[0] 3 | div = document.createElement('div'); 4 | a = document.createElement('a'); 5 | })(); -------------------------------------------------------------------------------- /examples/templates/06/detect-cookies/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /examples/templates/06/detect-cookies/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

This example detects whether third-party cookies are enabled or disabled by attempting to create a cookie inside an iframe hosted on a remote domain.

10 | 11 | 14 | 15 | 18 | 19 |

Having trouble?

20 | 21 |

Try restarting your browser when changing your third-party cookie settings. Also, try clearing your cookies for the test domain: {{ service_url }}.

22 | 23 | 43 | 44 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/07/clickjacking/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/templates/07/clickjacking/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

10 | In this example, a hidden iframe containing a button is made invisible and 11 | placed beneath the user's mouse cursor as they move around the page. If the 12 | user clicks, they will click the button inside the iframe, even though it is invisible. 13 |

14 | 15 |
16 | 17 | 28 | 29 | 42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/07/simple-xss/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

This example accepts and outputs user-contributed text as-is. The provided text inserts a malicious script.

10 | 11 |
12 | 14 |

15 | 16 |

17 |
18 | 19 |
20 | 21 | 32 | 33 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/07/xss-css/iframe-safe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | This text is not blue, because the style value was rejected 5 | 12 | 13 | -------------------------------------------------------------------------------- /examples/templates/07/xss-css/iframe-vulnerable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | This text is blue, but the script was injected 6 | 17 | 18 | -------------------------------------------------------------------------------- /examples/templates/07/xss-css/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

This page passes a publisher-defined CSS rule to two iframes via the query string. The rub: this rule contains an XSS exploit that affects certain versions of Internet Explorer.

10 | 11 |

12 | #00f;x:expression(alert('Hello, World!')); 13 |

14 | 15 |

Vulnerable

16 |
17 | 18 |

Safe

19 |
20 | 21 | 34 | 35 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/08/event-listeners/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

This code binds to the 'productWidget.rendered' event described in the book.

10 | 11 |

12 | 
13 |     
14 |     
42 | {% endblock %}


--------------------------------------------------------------------------------
/examples/templates/08/web-service-api/index.html:
--------------------------------------------------------------------------------
 1 | {% extends 'example.html' %}
 2 | 
 3 | {% block content %}
 4 |     
 5 |     
 8 | 
 9 |     

This code loads the Camera Stork SDK, then makes an API request using the SDK's Stork.api method. On success, the resulting JSON is posted below:

10 | 11 |
12 | 13 | 14 | 22 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/08/web-service-api/lib.js: -------------------------------------------------------------------------------- 1 | {% include 'shared/easyXDM.js' %} 2 | 3 | Stork.easyXDM = easyXDM.noConflict('Stork'); 4 | 5 | (function(Stork) { 6 | 7 | Stork.productWidget = function () { 8 | // NOTE: This is just a stub for now. The important thing is that it 9 | // broadcasts the productWidget.rendered event for demonstration 10 | // purposes. 11 | broadcast('productWidget.rendered'); 12 | }; 13 | 14 | var listeners = {}; 15 | 16 | Stork.listen = function(eventName, handler) { 17 | if (typeof listeners[eventName] === 'undefined') 18 | listeners[eventName] = []; 19 | 20 | listeners[eventName].push(handler); 21 | }; 22 | 23 | Stork.unlisten = function(eventName, handler) { 24 | if (!listeners[eventName]) 25 | return; 26 | 27 | for (var i = 0; i < listeners[eventName].length; i++) { 28 | if (listeners[eventName][i] === handler) { 29 | listeners[eventName].splice(i, 1); 30 | break; 31 | } 32 | } 33 | }; 34 | 35 | function broadcast(eventName) { 36 | if (!listeners[eventName]) 37 | return; 38 | 39 | for (var i = 0; i < listeners[eventName].length; i++) { 40 | listeners[eventName][i](); 41 | } 42 | }; 43 | 44 | var rpc; 45 | 46 | Stork.api = function() { 47 | Stork.rpc.apiTunnel.apply(this, arguments); 48 | }; 49 | 50 | Stork.rpc = null; 51 | 52 | Stork._initializeLibrary = function (callback) { 53 | Stork.rpc = new Stork.easyXDM.Rpc({ 54 | remote: "{{ service_url_for('example', chapter='08', name='web-service-api', file='tunnel.html') }}", 55 | onReady: callback 56 | }, { 57 | remote: { 58 | apiTunnel: {} // remote stub 59 | } 60 | }); 61 | } 62 | 63 | 64 | })(Stork); 65 | -------------------------------------------------------------------------------- /examples/templates/08/web-service-api/sdk.js: -------------------------------------------------------------------------------- 1 | (function(window, undefined) { 2 | var Stork = {}; 3 | 4 | if (window.Stork) 5 | return; 6 | 7 | Stork.init = function(callback) { 8 | Stork.script("{{ service_url_for('example', chapter='08', name='web-service-api', file='lib.js') }}", function () { 9 | Stork._initializeLibrary(callback); 10 | }); 11 | }; 12 | 13 | window.Stork = Stork; 14 | })(this); 15 | 16 | // Embed the 'script' microlib into our initial script file for loading dependencies. 17 | 18 | {% include 'shared/script.js' %} 19 | 20 | Stork.script = $script.noConflict(); 21 | -------------------------------------------------------------------------------- /examples/templates/08/web-service-api/tunnel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 38 | -------------------------------------------------------------------------------- /examples/templates/09/deferred-avatars/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

On this page are 10 img elements whose contents are deferred. As you scroll down the page, and the img elements enter the viewport, the correct src is swapped in.

10 | 11 | 15 | 16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | 27 | 28 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/09/deferred-widget/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'example.html' %} 2 | 3 | {% block content %} 4 | 5 | 8 | 9 |

At the bottom of this page is a blue rectangle representing the widget element. Scroll down. 10 | The JavaScript code on this page will fire an alert once you've reached it and stopped scrolling.

11 | 12 | 20 | 21 |
22 | 23 | 24 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/10/hiro/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tests - Hiro 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 |
32 |

Hiro

33 |
34 | 35 |
36 | 39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 |
47 |
48 | 49 | 52 |
53 | 54 | 78 | 79 | 104 | 105 | 120 | 121 | 125 | 126 | 133 | 134 | 146 | 147 | 171 | 172 | 173 | -------------------------------------------------------------------------------- /examples/templates/10/qunit/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | QUnit Test Suite 8 | 9 | 10 | 11 | 12 | 24 | 25 | 26 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/templates/example.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block header %} 4 | 5 | 8 | 9 | {% endblock %} -------------------------------------------------------------------------------- /examples/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html' %} 2 | 3 | {% block content %} 4 | 7 | 8 |

Chapter 1

9 | 13 | 14 |

Chapter 2

15 | 20 | 21 |

Chapter 3

22 | 27 | 28 |

Chapter 4

29 | 34 | 35 |

Chapter 5

36 | 41 | 42 |

Chapter 6

43 | 47 | 48 |

Chapter 7

49 | 54 | 55 |

Chapter 8

56 | 60 | 61 |

Chapter 9

62 | 66 | 67 |

Chapter 10

68 | 72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /examples/templates/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% block head %} 5 | Third-party JavaScript 6 | 7 | 8 | {% endblock %} 9 | 10 | 11 | 12 |
13 | {% block header %}{% endblock %} 14 | 15 | {% block content %}{% endblock %} 16 | 17 | 20 |
21 | 22 | -------------------------------------------------------------------------------- /examples/templates/shared/script.js: -------------------------------------------------------------------------------- 1 | !function (name, definition) { 2 | if (typeof define == 'function') define(definition) 3 | else if (typeof module != 'undefined') module.exports = definition() 4 | else this[name] = definition() 5 | }('$script', function() { 6 | var win = this, doc = document 7 | , head = doc.getElementsByTagName('head')[0] 8 | , validBase = /^https?:\/\// 9 | , old = win.$script, list = {}, ids = {}, delay = {}, scriptpath 10 | , scripts = {}, s = 'string', f = false 11 | , push = 'push', domContentLoaded = 'DOMContentLoaded', readyState = 'readyState' 12 | , addEventListener = 'addEventListener', onreadystatechange = 'onreadystatechange' 13 | 14 | function every(ar, fn, i) { 15 | for (i = 0, j = ar.length; i < j; ++i) if (!fn(ar[i])) return f 16 | return 1 17 | } 18 | function each(ar, fn) { 19 | every(ar, function(el) { 20 | return !fn(el) 21 | }) 22 | } 23 | 24 | if (!doc[readyState] && doc[addEventListener]) { 25 | doc[addEventListener](domContentLoaded, function fn() { 26 | doc.removeEventListener(domContentLoaded, fn, f) 27 | doc[readyState] = 'complete' 28 | }, f) 29 | doc[readyState] = 'loading' 30 | } 31 | 32 | function $script(paths, idOrDone, optDone) { 33 | paths = paths[push] ? paths : [paths] 34 | var idOrDoneIsDone = idOrDone && idOrDone.call 35 | , done = idOrDoneIsDone ? idOrDone : optDone 36 | , id = idOrDoneIsDone ? paths.join('') : idOrDone 37 | , queue = paths.length 38 | function loopFn(item) { 39 | return item.call ? item() : list[item] 40 | } 41 | function callback() { 42 | if (!--queue) { 43 | list[id] = 1 44 | done && done() 45 | for (var dset in delay) { 46 | every(dset.split('|'), loopFn) && !each(delay[dset], loopFn) && (delay[dset] = []) 47 | } 48 | } 49 | } 50 | setTimeout(function () { 51 | each(paths, function(path) { 52 | if (scripts[path]) { 53 | id && (ids[id] = 1) 54 | return scripts[path] == 2 && callback() 55 | } 56 | scripts[path] = 1 57 | id && (ids[id] = 1) 58 | create(!validBase.test(path) && scriptpath ? scriptpath + path + '.js' : path, callback) 59 | }) 60 | }, 0) 61 | return $script 62 | } 63 | 64 | function create(path, fn) { 65 | var el = doc.createElement('script') 66 | , loaded = f 67 | el.onload = el.onerror = el[onreadystatechange] = function () { 68 | if ((el[readyState] && !(/^c|loade/.test(el[readyState]))) || loaded) return; 69 | el.onload = el[onreadystatechange] = null 70 | loaded = 1 71 | scripts[path] = 2 72 | fn() 73 | } 74 | el.async = 1 75 | el.src = path 76 | head.insertBefore(el, head.firstChild) 77 | } 78 | 79 | $script.get = create 80 | 81 | $script.order = function (scripts, id, done) { 82 | (function callback(s) { 83 | s = scripts.shift() 84 | if (!scripts.length) $script(s, id, done) 85 | else $script(s, callback) 86 | }()) 87 | } 88 | 89 | $script.path = function(p) { 90 | scriptpath = p 91 | } 92 | $script.ready = function(deps, ready, req) { 93 | deps = deps[push] ? deps : [deps] 94 | var missing = []; 95 | !each(deps, function(dep) { 96 | list[dep] || missing[push](dep); 97 | }) && every(deps, function(dep) {return list[dep]}) ? 98 | ready() : !function(key) { 99 | delay[key] = delay[key] || [] 100 | delay[key][push](ready) 101 | req && req(missing) 102 | }(deps.join('|')) 103 | return $script 104 | } 105 | 106 | $script.noConflict = function () { 107 | win.$script = old; 108 | return this 109 | } 110 | 111 | return $script 112 | }) -------------------------------------------------------------------------------- /freeze.py: -------------------------------------------------------------------------------- 1 | import os 2 | import string 3 | 4 | from flask_frozen import Freezer 5 | from examples import app 6 | 7 | freezer = Freezer(app) 8 | 9 | 10 | @freezer.register_generator 11 | def example_url_generator(): 12 | out = [] 13 | for dirname, dirnames, filenames in os.walk(os.path.join('examples', 'templates')): 14 | if dirnames == []: 15 | parts = string.split(dirname, os.sep) 16 | for f in filenames: 17 | try: 18 | out.append(('example', {'chapter': parts[2], 'name': parts[3], 'file': f})) 19 | except IndexError: 20 | # Ignore 21 | pass 22 | return out 23 | 24 | if __name__ == '__main__': 25 | freezer.freeze() 26 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import time 2 | import flask 3 | from flask import Flask, make_response, request 4 | 5 | app = Flask(__name__, static_url_path='/examples', static_folder='build') 6 | 7 | 8 | def get_weather_data(zip): 9 | """ 10 | This is a stubbed function that returns the same static 11 | data each time. In a real server environment, this would 12 | query the database with the given zip parameter. 13 | """ 14 | 15 | return { 16 | 'location': 'San Francisco', 17 | 'image': '/examples/common/partly_cloudy.png', 18 | 'temp': 78, 19 | 'desc': 'Partly Cloudy' 20 | } 21 | 22 | 23 | @app.route('/widget.js') 24 | def weather_widget(): 25 | """ 26 | This is the server-side implementation of the weathernerby.com widget 27 | from Chapter 1 28 | """ 29 | zip = request.args.get('zip') 30 | data = get_weather_data(zip) 31 | 32 | out = ''' 33 | document.write( 34 | '
' + 35 | '

%s

' + 36 | ' ' + 37 | '

%s °F — %s

' + 38 | '
' 39 | ) 40 | ''' % (data['location'], data['image'], data['temp'], data['desc']) 41 | 42 | response = make_response(out) 43 | response.headers['Content-Type'] = 'application/javascript' 44 | 45 | return response 46 | 47 | 48 | @app.route('/slow') 49 | def slow(): 50 | """ 51 | Wait 3 seconds before redirecting to the requested resource 52 | """ 53 | time.sleep(3) 54 | return flask.redirect(request.args.get('url')) 55 | 56 | 57 | @app.route('/echo.jsonp') 58 | def jsonp(): 59 | """ 60 | Returns the passed query string args via JSONP. 61 | """ 62 | return "%s(%s)" % (request.args.get('callback'), json.dumps(request.args)) 63 | 64 | 65 | @app.route('/cors') 66 | def cors(): 67 | r = make_response(json.dumps(request.args)) 68 | 69 | # Must match requesting Origin or * 70 | r.headers['Access-Control-Allow-Origin'] = '*' 71 | return r 72 | 73 | 74 | #======================================== 75 | # API 76 | #======================================== 77 | 78 | def trusted_domains(domain): 79 | return ['publisher.dev'] 80 | 81 | 82 | def api_request(request, endpoint): 83 | return make_response(json.dumps({'name': 'Mikon 9000'})) 84 | 85 | from urlparse import urlparse 86 | import json 87 | 88 | 89 | @app.route('/api/') 90 | def api(endpoint=None): 91 | domain = urlparse(request.headers['Referer']).hostname 92 | if domain == 'widget.dev': 93 | domain = urlparse(request.headers['CameraStork-Publisher-Origin']).hostname 94 | 95 | api_key = request.args.get('apiKey') 96 | 97 | if domain in trusted_domains(api_key): 98 | return api_request(request, endpoint) 99 | else: 100 | return make_response('Unauthorized domain: %s' % domain, 403) 101 | 102 | 103 | @app.route('/login', methods=['POST']) 104 | def login(): 105 | """ 106 | Sets a cookie 107 | """ 108 | # return flask.redirect('/examples/06/auth-new-window/success.html') 109 | r = make_response(flask.redirect('/examples/06/auth-new-window/success.html')) 110 | r.set_cookie('session_id', 'abcdef123456') 111 | return r 112 | 113 | if __name__ == '__main__': 114 | app.debug = True 115 | app.run() 116 | --------------------------------------------------------------------------------