1<!-- 2@license 3Copyright (c) 2015 The Polymer Project Authors. All rights reserved. 4This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt 5The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt 6The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt 7Code distributed by Google as part of the polymer project is also 8subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt 9--> 10<link rel="import" href="../polymer/polymer.html"> 11 12<script> 13(function() { 14 "use strict"; 15 /** 16 * `Polymer.IronJsonpLibraryBehavior` loads a jsonp library. 17 * Multiple components can request same library, only one copy will load. 18 * 19 * Some libraries require a specific global function be defined. 20 * If this is the case, specify the `callbackName` property. 21 * 22 * You should use an HTML Import to load library dependencies 23 * when possible instead of using this element. 24 * 25 * @hero hero.svg 26 * @demo demo/index.html 27 * @polymerBehavior 28 */ 29 Polymer.IronJsonpLibraryBehavior = { 30 31 properties: { 32 /** 33 * True if library has been successfully loaded 34 */ 35 libraryLoaded: { 36 type: Boolean, 37 value: false, 38 notify: true, 39 readOnly: true 40 }, 41 /** 42 * Not null if library has failed to load 43 */ 44 libraryErrorMessage: { 45 type: String, 46 value: null, 47 notify: true, 48 readOnly: true 49 } 50 // Following properties are to be set by behavior users 51 /** 52 * Library url. Must contain string `%%callback%%`. 53 * 54 * `%%callback%%` is a placeholder for jsonp wrapper function name 55 * 56 * Ex: https://maps.googleapis.com/maps/api/js?callback=%%callback%% 57 * @property libraryUrl 58 */ 59 /** 60 * Set if library requires specific callback name. 61 * Name will be automatically generated if not set. 62 * @property callbackName 63 */ 64 /** 65 * name of event to be emitted when library loads. Standard is `api-load` 66 * @property notifyEvent 67 */ 68 /** 69 * event with name specified in `notifyEvent` attribute 70 * will fire upon successful load2 71 * @event `notifyEvent` 72 */ 73 }, 74 75 observers: [ 76 '_libraryUrlChanged(libraryUrl)' 77 ], 78 79 _libraryUrlChanged: function(libraryUrl) { 80 // can't load before ready because notifyEvent might not be set 81 if (this._isReady && this.libraryUrl) 82 this._loadLibrary(); 83 }, 84 85 _libraryLoadCallback: function(err, result) { 86 if (err) { 87 Polymer.Base._warn("Library load failed:", err.message); 88 this._setLibraryErrorMessage(err.message); 89 } 90 else { 91 this._setLibraryErrorMessage(null); 92 this._setLibraryLoaded(true); 93 if (this.notifyEvent) 94 this.fire(this.notifyEvent, result, {composed: true}); 95 } 96 }, 97 98 /** loads the library, and fires this.notifyEvent upon completion */ 99 _loadLibrary: function() { 100 LoaderMap.require( 101 this.libraryUrl, 102 this._libraryLoadCallback.bind(this), 103 this.callbackName 104 ); 105 }, 106 107 ready: function() { 108 this._isReady = true; 109 if (this.libraryUrl) 110 this._loadLibrary(); 111 } 112 }; 113 114 /** 115 * LoaderMap keeps track of all Loaders 116 */ 117 var LoaderMap = { 118 apiMap: {}, // { hash -> Loader } 119 120 /** 121 * @param {Function} notifyCallback loaded callback fn(result) 122 * @param {string} jsonpCallbackName name of jsonpcallback. If API does not provide it, leave empty. Optional. 123 */ 124 require: function(url, notifyCallback, jsonpCallbackName) { 125 126 // make hashable string form url 127 var name = this.nameFromUrl(url); 128 129 // create a loader as needed 130 if (!this.apiMap[name]) 131 this.apiMap[name] = new Loader(name, url, jsonpCallbackName); 132 133 // ask for notification 134 this.apiMap[name].requestNotify(notifyCallback); 135 }, 136 137 nameFromUrl: function(url) { 138 return url.replace(/[\:\/\%\?\&\.\=\-\,]/g, '_') + '_api'; 139 } 140 }; 141 142 /** @constructor */ 143 var Loader = function(name, url, callbackName) { 144 this.notifiers = []; // array of notifyFn [ notifyFn* ] 145 146 // callback is specified either as callback name 147 // or computed dynamically if url has callbackMacro in it 148 if (!callbackName) { 149 if (url.indexOf(this.callbackMacro) >= 0) { 150 callbackName = name + '_loaded'; 151 url = url.replace(this.callbackMacro, callbackName); 152 } else { 153 this.error = new Error('IronJsonpLibraryBehavior a %%callback%% parameter is required in libraryUrl'); 154 // TODO(sjmiles): we should probably fallback to listening to script.load 155 return; 156 } 157 } 158 this.callbackName = callbackName; 159 window[this.callbackName] = this.success.bind(this); 160 this.addScript(url); 161 }; 162 163 Loader.prototype = { 164 165 callbackMacro: '%%callback%%', 166 loaded: false, 167 168 addScript: function(src) { 169 var script = document.createElement('script'); 170 script.src = src; 171 script.onerror = this.handleError.bind(this); 172 var s = document.querySelector('script') || document.body; 173 s.parentNode.insertBefore(script, s); 174 this.script = script; 175 }, 176 177 removeScript: function() { 178 if (this.script.parentNode) { 179 this.script.parentNode.removeChild(this.script); 180 } 181 this.script = null; 182 }, 183 184 handleError: function(ev) { 185 this.error = new Error("Library failed to load"); 186 this.notifyAll(); 187 this.cleanup(); 188 }, 189 190 success: function() { 191 this.loaded = true; 192 this.result = Array.prototype.slice.call(arguments); 193 this.notifyAll(); 194 this.cleanup(); 195 }, 196 197 cleanup: function() { 198 delete window[this.callbackName]; 199 }, 200 201 notifyAll: function() { 202 this.notifiers.forEach( function(notifyCallback) { 203 notifyCallback(this.error, this.result); 204 }.bind(this)); 205 this.notifiers = []; 206 }, 207 208 requestNotify: function(notifyCallback) { 209 if (this.loaded || this.error) { 210 notifyCallback( this.error, this.result); 211 } else { 212 this.notifiers.push(notifyCallback); 213 } 214 } 215 }; 216})(); 217</script> 218 219<!-- 220 Loads specified jsonp library. 221 222 Example: 223 224 <iron-jsonp-library 225 library-url="https://apis.google.com/js/plusone.js?onload=%%callback%%" 226 notify-event="api-load" 227 library-loaded="{{loaded}}"></iron-jsonp-library> 228 229 Will emit 'api-load' event when loaded, and set 'loaded' to true 230 231 Implemented by Polymer.IronJsonpLibraryBehavior. Use it 232 to create specific library loader elements. 233 234 @demo 235--> 236<script> 237 Polymer({ 238 239 is: 'iron-jsonp-library', 240 241 behaviors: [ Polymer.IronJsonpLibraryBehavior ], 242 243 properties: { 244 /** 245 * Library url. Must contain string `%%callback%%`. 246 * 247 * `%%callback%%` is a placeholder for jsonp wrapper function name 248 * 249 * Ex: https://maps.googleapis.com/maps/api/js?callback=%%callback%% 250 */ 251 libraryUrl: String, 252 /** 253 * Set if library requires specific callback name. 254 * Name will be automatically generated if not set. 255 */ 256 callbackName: String, 257 /** 258 * event with name specified in 'notifyEvent' attribute 259 * will fire upon successful load 260 */ 261 notifyEvent: String 262 /** 263 * event with name specified in 'notifyEvent' attribute 264 * will fire upon successful load 265 * @event `notifyEvent` 266 */ 267 268 } 269 }); 270 271</script> 272