| 1 | /** | | |
| 2 | * SVGInject - Version 1.2.3 | | |
| 3 | * A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM. | | |
| 4 | * | | |
| 5 | * https://github.com/iconfu/svg-inject | | |
| 6 | * | | |
| 7 | * Copyright (c) 2018 INCORS, the creators of iconfu.com | | |
| 8 | * @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE | | |
| 9 | */ | | |
| 10 | | | |
| 11 | (function(window, document) { | | |
| 12 | // constants for better minification | | |
| 13 | var _CREATE_ELEMENT_ = 'createElement'; | | |
| 14 | var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName'; | | |
| 15 | var _LENGTH_ = 'length'; | | |
| 16 | var _STYLE_ = 'style'; | | |
| 17 | var _TITLE_ = 'title'; | | |
| 18 | var _UNDEFINED_ = 'undefined'; | | |
| 19 | var _SET_ATTRIBUTE_ = 'setAttribute'; | | |
| 20 | var _GET_ATTRIBUTE_ = 'getAttribute'; | | |
| 21 | | | |
| 22 | var NULL = null; | | |
| 23 | | | |
| 24 | // constants | | |
| 25 | var __SVGINJECT = '__svgInject'; | | |
| 26 | var ID_SUFFIX = '--inject-'; | | |
| 27 | var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g"); | | |
| 28 | var LOAD_FAIL = 'LOAD_FAIL'; | | |
| 29 | var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED'; | | |
| 30 | var SVG_INVALID = 'SVG_INVALID'; | | |
| 31 | var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror']; | | |
| 32 | var A_ELEMENT = document[_CREATE_ELEMENT_]('a'); | | |
| 33 | var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_; | | |
| 34 | var DEFAULT_OPTIONS = { | | |
| 35 | useCache: true, | | |
| 36 | copyAttributes: true, | | |
| 37 | makeIdsUnique: true | | |
| 38 | }; | | |
| 39 | // Map of IRI referenceable tag names to properties that can reference them. This is defined in | | |
| 40 | // https://www.w3.org/TR/SVG11/linking.html#processingIRI | | |
| 41 | var IRI_TAG_PROPERTIES_MAP = { | | |
| 42 | clipPath: ['clip-path'], | | |
| 43 | 'color-profile': NULL, | | |
| 44 | cursor: NULL, | | |
| 45 | filter: NULL, | | |
| 46 | linearGradient: ['fill', 'stroke'], | | |
| 47 | marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'], | | |
| 48 | mask: NULL, | | |
| 49 | pattern: ['fill', 'stroke'], | | |
| 50 | radialGradient: ['fill', 'stroke'] | | |
| 51 | }; | | |
| 52 | var INJECTED = 1; | | |
| 53 | var FAIL = 2; | | |
| 54 | | | |
| 55 | var uniqueIdCounter = 1; | | |
| 56 | var xmlSerializer; | | |
| 57 | var domParser; | | |
| 58 | | | |
| 59 | | | |
| 60 | // creates an SVG document from an SVG string | | |
| 61 | function svgStringToSvgDoc(svgStr) { | | |
| 62 | domParser = domParser || new DOMParser(); | | |
| 63 | return domParser.parseFromString(svgStr, 'text/xml'); | | |
| 64 | } | | |
| 65 | | | |
| 66 | | | |
| 67 | // searializes an SVG element to an SVG string | | |
| 68 | function svgElemToSvgString(svgElement) { | | |
| 69 | xmlSerializer = xmlSerializer || new XMLSerializer(); | | |
| 70 | return xmlSerializer.serializeToString(svgElement); | | |
| 71 | } | | |
| 72 | | | |
| 73 | | | |
| 74 | // Returns the absolute url for the specified url | | |
| 75 | function getAbsoluteUrl(url) { | | |
| 76 | A_ELEMENT.href = url; | | |
| 77 | return A_ELEMENT.href; | | |
| 78 | } | | |
| 79 | | | |
| 80 | | | |
| 81 | // Load svg with an XHR request | | |
| 82 | function loadSvg(url, callback, errorCallback) { | | |
| 83 | if (url) { | | |
| 84 | var req = new XMLHttpRequest(); | | |
| 85 | req.onreadystatechange = function() { | | |
| 86 | if (req.readyState == 4) { | | |
| 87 | // readyState is DONE | | |
| 88 | var status = req.status; | | |
| 89 | if (status == 200) { | | |
| 90 | // request status is OK | | |
| 91 | callback(req.responseXML, req.responseText.trim()); | | |
| 92 | } else if (status >= 400) { | | |
| 93 | // request status is error (4xx or 5xx) | | |
| 94 | errorCallback(); | | |
| 95 | } else if (status == 0) { | | |
| 96 | // request status 0 can indicate a failed cross-domain call | | |
| 97 | errorCallback(); | | |
| 98 | } | | |
| 99 | } | | |
| 100 | }; | | |
| 101 | req.open('GET', url, true); | | |
| 102 | req.send(); | | |
| 103 | } | | |
| 104 | } | | |
| 105 | | | |
| 106 | | | |
| 107 | // Copy attributes from img element to svg element | | |
| 108 | function copyAttributes(imgElem, svgElem) { | | |
| 109 | var attribute; | | |
| 110 | var attributeName; | | |
| 111 | var attributeValue; | | |
| 112 | var attributes = imgElem.attributes; | | |
| 113 | for (var i = 0; i < attributes[_LENGTH_]; i++) { | | |
| 114 | attribute = attributes[i]; | | |
| 115 | attributeName = attribute.name; | | |
| 116 | // Only copy attributes not explicitly excluded from copying | | |
| 117 | if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) { | | |
| 118 | attributeValue = attribute.value; | | |
| 119 | // If img attribute is "title", insert a title element into SVG element | | |
| 120 | if (attributeName == _TITLE_) { | | |
| 121 | var titleElem; | | |
| 122 | var firstElementChild = svgElem.firstElementChild; | | |
| 123 | if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) { | | |
| 124 | // If the SVG element's first child is a title element, keep it as the title element | | |
| 125 | titleElem = firstElementChild; | | |
| 126 | } else { | | |
| 127 | // If the SVG element's first child element is not a title element, create a new title | | |
| 128 | // ele,emt and set it as the first child | | |
| 129 | titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_); | | |
| 130 | svgElem.insertBefore(titleElem, firstElementChild); | | |
| 131 | } | | |
| 132 | // Set new title content | | |
| 133 | titleElem.textContent = attributeValue; | | |
| 134 | } else { | | |
| 135 | // Set img attribute to svg element | | |
| 136 | svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue); | | |
| 137 | } | | |
| 138 | } | | |
| 139 | } | | |
| 140 | } | | |
| 141 | | | |
| 142 | | | |
| 143 | // This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision | | |
| 144 | // between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is | | |
| 145 | // incremented with each injection. References to the IDs are adjusted accordingly. | | |
| 146 | // We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one | | |
| 147 | // injected SVG. | | |
| 148 | // If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG | | |
| 149 | function makeIdsUnique(svgElem, onlyReferenced) { | | |
| 150 | var idSuffix = ID_SUFFIX + uniqueIdCounter++; | | |
| 151 | // Regular expression for functional notations of an IRI references. This will find occurences in the form | | |
| 152 | // url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID | | |
| 153 | var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g; | | |
| 154 | // Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but | | |
| 155 | // this is not a requirement, therefore we have to search for IDs in the whole SVG. | | |
| 156 | var idElements = svgElem.querySelectorAll('[id]'); | | |
| 157 | var idElem; | | |
| 158 | // An object containing referenced IDs as keys is used if only referenced IDs should be uniquified. | | |
| 159 | // If this object does not exist, all IDs will be uniquified. | | |
| 160 | var referencedIds = onlyReferenced ? [] : NULL; | | |
| 161 | var tagName; | | |
| 162 | var iriTagNames = {}; | | |
| 163 | var iriProperties = []; | | |
| 164 | var changed = false; | | |
| 165 | var i, j; | | |
| 166 | | | |
| 167 | if (idElements[_LENGTH_]) { | | |
| 168 | // Make all IDs unique by adding the ID suffix and collect all encountered tag names | | |
| 169 | // that are IRI referenceable from properities. | | |
| 170 | for (i = 0; i < idElements[_LENGTH_]; i++) { | | |
| 171 | tagName = idElements[i].localName; // Use non-namespaced tag name | | |
| 172 | // Make ID unique if tag name is IRI referenceable | | |
| 173 | if (tagName in IRI_TAG_PROPERTIES_MAP) { | | |
| 174 | iriTagNames[tagName] = 1; | | |
| 175 | } | | |
| 176 | } | | |
| 177 | // Get all properties that are mapped to the found IRI referenceable tags | | |
| 178 | for (tagName in iriTagNames) { | | |
| 179 | (IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) { | | |
| 180 | // Add mapped properties to array of iri referencing properties. | | |
| 181 | // Use linear search here because the number of possible entries is very small (maximum 11) | | |
| 182 | if (iriProperties.indexOf(mappedProperty) < 0) { | | |
| 183 | iriProperties.push(mappedProperty); | | |
| 184 | } | | |
| 185 | }); | | |
| 186 | } | | |
| 187 | if (iriProperties[_LENGTH_]) { | | |
| 188 | // Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"' | | |
| 189 | iriProperties.push(_STYLE_); | | |
| 190 | } | | |
| 191 | // Run through all elements of the SVG and replace IDs in references. | | |
| 192 | // To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*'). | | |
| 193 | // Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately. | | |
| 194 | var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*'); | | |
| 195 | var element = svgElem; | | |
| 196 | var propertyName; | | |
| 197 | var value; | | |
| 198 | var newValue; | | |
| 199 | for (i = -1; element != NULL;) { | | |
| 200 | if (element.localName == _STYLE_) { | | |
| 201 | // If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content | | |
| 202 | value = element.textContent; | | |
| 203 | newValue = value && value.replace(funcIriRegex, function(match, id) { | | |
| 204 | if (referencedIds) { | | |
| 205 | referencedIds[id] = 1; | | |
| 206 | } | | |
| 207 | return 'url(#' + id + idSuffix + ')'; | | |
| 208 | }); | | |
| 209 | if (newValue !== value) { | | |
| 210 | element.textContent = newValue; | | |
| 211 | } | | |
| 212 | } else if (element.hasAttributes()) { | | |
| 213 | // Run through all property names for which IDs were found | | |
| 214 | for (j = 0; j < iriProperties[_LENGTH_]; j++) { | | |
| 215 | propertyName = iriProperties[j]; | | |
| 216 | value = element[_GET_ATTRIBUTE_](propertyName); | | |
| 217 | newValue = value && value.replace(funcIriRegex, function(match, id) { | | |
| 218 | if (referencedIds) { | | |
| 219 | referencedIds[id] = 1; | | |
| 220 | } | | |
| 221 | return 'url(#' + id + idSuffix + ')'; | | |
| 222 | }); | | |
| 223 | if (newValue !== value) { | | |
| 224 | element[_SET_ATTRIBUTE_](propertyName, newValue); | | |
| 225 | } | | |
| 226 | } | | |
| 227 | // Replace IDs in xlink:ref and href attributes | | |
| 228 | ['xlink:href', 'href'].forEach(function(refAttrName) { | | |
| 229 | var iri = element[_GET_ATTRIBUTE_](refAttrName); | | |
| 230 | if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference | | |
| 231 | iri = iri.trim(); | | |
| 232 | element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix); | | |
| 233 | if (referencedIds) { | | |
| 234 | // Add ID to referenced IDs | | |
| 235 | referencedIds[iri.substring(1)] = 1; | | |
| 236 | } | | |
| 237 | } | | |
| 238 | }); | | |
| 239 | } | | |
| 240 | element = descElements[++i]; | | |
| 241 | } | | |
| 242 | for (i = 0; i < idElements[_LENGTH_]; i++) { | | |
| 243 | idElem = idElements[i]; | | |
| 244 | // If set of referenced IDs exists, make only referenced IDs unique, | | |
| 245 | // otherwise make all IDs unique. | | |
| 246 | if (!referencedIds || referencedIds[idElem.id]) { | | |
| 247 | // Add suffix to element's ID | | |
| 248 | idElem.id += idSuffix; | | |
| 249 | changed = true; | | |
| 250 | } | | |
| 251 | } | | |
| 252 | } | | |
| 253 | // return true if SVG element has changed | | |
| 254 | return changed; | | |
| 255 | } | | |
| 256 | | | |
| 257 | | | |
| 258 | // For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a | | |
| 259 | // higher ID counter. This is much more performant than a call to makeIdsUnique(). | | |
| 260 | function makeIdsUniqueCached(svgString) { | | |
| 261 | return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++); | | |
| 262 | } | | |
| 263 | | | |
| 264 | | | |
| 265 | // Inject SVG by replacing the img element with the SVG element in the DOM | | |
| 266 | function inject(imgElem, svgElem, absUrl, options) { | | |
| 267 | if (svgElem) { | | |
| 268 | svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl); | | |
| 269 | var parentNode = imgElem.parentNode; | | |
| 270 | if (parentNode) { | | |
| 271 | if (options.copyAttributes) { | | |
| 272 | copyAttributes(imgElem, svgElem); | | |
| 273 | } | | |
| 274 | // Invoke beforeInject hook if set | | |
| 275 | var beforeInject = options.beforeInject; | | |
| 276 | var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem; | | |
| 277 | // Replace img element with new element. This is the actual injection. | | |
| 278 | parentNode.replaceChild(injectElem, imgElem); | | |
| 279 | // Mark img element as injected | | |
| 280 | imgElem[__SVGINJECT] = INJECTED; | | |
| 281 | removeOnLoadAttribute(imgElem); | | |
| 282 | // Invoke afterInject hook if set | | |
| 283 | var afterInject = options.afterInject; | | |
| 284 | if (afterInject) { | | |
| 285 | afterInject(imgElem, injectElem); | | |
| 286 | } | | |
| 287 | } | | |
| 288 | } else { | | |
| 289 | svgInvalid(imgElem, options); | | |
| 290 | } | | |
| 291 | } | | |
| 292 | | | |
| 293 | | | |
| 294 | // Merges any number of options objects into a new object | | |
| 295 | function mergeOptions() { | | |
| 296 | var mergedOptions = {}; | | |
| 297 | var args = arguments; | | |
| 298 | // Iterate over all specified options objects and add all properties to the new options object | | |
| 299 | for (var i = 0; i < args[_LENGTH_]; i++) { | | |
| 300 | var argument = args[i]; | | |
| 301 | for (var key in argument) { | | |
| 302 | if (argument.hasOwnProperty(key)) { | | |
| 303 | mergedOptions[key] = argument[key]; | | |
| 304 | } | | |
| 305 | } | | |
| 306 | } | | |
| 307 | return mergedOptions; | | |
| 308 | } | | |
| 309 | | | |
| 310 | | | |
| 311 | // Adds the specified CSS to the document's <head> element | | |
| 312 | function addStyleToHead(css) { | | |
| 313 | var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0]; | | |
| 314 | if (head) { | | |
| 315 | var style = document[_CREATE_ELEMENT_](_STYLE_); | | |
| 316 | style.type = 'text/css'; | | |
| 317 | style.appendChild(document.createTextNode(css)); | | |
| 318 | head.appendChild(style); | | |
| 319 | } | | |
| 320 | } | | |
| 321 | | | |
| 322 | | | |
| 323 | // Builds an SVG element from the specified SVG string | | |
| 324 | function buildSvgElement(svgStr, verify) { | | |
| 325 | if (verify) { | | |
| 326 | var svgDoc; | | |
| 327 | try { | | |
| 328 | // Parse the SVG string with DOMParser | | |
| 329 | svgDoc = svgStringToSvgDoc(svgStr); | | |
| 330 | } catch(e) { | | |
| 331 | return NULL; | | |
| 332 | } | | |
| 333 | if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) { | | |
| 334 | // DOMParser does not throw an exception, but instead puts parsererror tags in the document | | |
| 335 | return NULL; | | |
| 336 | } | | |
| 337 | return svgDoc.documentElement; | | |
| 338 | } else { | | |
| 339 | var div = document.createElement('div'); | | |
| 340 | div.innerHTML = svgStr; | | |
| 341 | return div.firstElementChild; | | |
| 342 | } | | |
| 343 | } | | |
| 344 | | | |
| 345 | | | |
| 346 | function removeOnLoadAttribute(imgElem) { | | |
| 347 | // Remove the onload attribute. Should only be used to remove the unstyled image flash protection and | | |
| 348 | // make the element visible, not for removing the event listener. | | |
| 349 | imgElem.removeAttribute('onload'); | | |
| 350 | } | | |
| 351 | | | |
| 352 | | | |
| 353 | function errorMessage(msg) { | | |
| 354 | console.error('SVGInject: ' + msg); | | |
| 355 | } | | |
| 356 | | | |
| 357 | | | |
| 358 | function fail(imgElem, status, options) { | | |
| 359 | imgElem[__SVGINJECT] = FAIL; | | |
| 360 | if (options.onFail) { | | |
| 361 | options.onFail(imgElem, status); | | |
| 362 | } else { | | |
| 363 | errorMessage(status); | | |
| 364 | } | | |
| 365 | } | | |
| 366 | | | |
| 367 | | | |
| 368 | function svgInvalid(imgElem, options) { | | |
| 369 | removeOnLoadAttribute(imgElem); | | |
| 370 | fail(imgElem, SVG_INVALID, options); | | |
| 371 | } | | |
| 372 | | | |
| 373 | | | |
| 374 | function svgNotSupported(imgElem, options) { | | |
| 375 | removeOnLoadAttribute(imgElem); | | |
| 376 | fail(imgElem, SVG_NOT_SUPPORTED, options); | | |
| 377 | } | | |
| 378 | | | |
| 379 | | | |
| 380 | function loadFail(imgElem, options) { | | |
| 381 | fail(imgElem, LOAD_FAIL, options); | | |
| 382 | } | | |
| 383 | | | |
| 384 | | | |
| 385 | function removeEventListeners(imgElem) { | | |
| 386 | imgElem.onload = NULL; | | |
| 387 | imgElem.onerror = NULL; | | |
| 388 | } | | |
| 389 | | | |
| 390 | | | |
| 391 | function imgNotSet(msg) { | | |
| 392 | errorMessage('no img element'); | | |
| 393 | } | | |
| 394 | | | |
| 395 | | | |
| 396 | function createSVGInject(globalName, options) { | | |
| 397 | var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options); | | |
| 398 | var svgLoadCache = {}; | | |
| 399 | | | |
| 400 | if (IS_SVG_SUPPORTED) { | | |
| 401 | // If the browser supports SVG, add a small stylesheet that hides the <img> elements until | | |
| 402 | // injection is finished. This avoids showing the unstyled SVGs before style is applied. | | |
| 403 | addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}'); | | |
| 404 | } | | |
| 405 | | | |
| 406 | | | |
| 407 | /** | | |
| 408 | * SVGInject | | |
| 409 | * | | |
| 410 | * Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img` | | |
| 411 | * elements. Returns a Promise object which resolves if all passed in `img` elements have either been | | |
| 412 | * injected or failed to inject (Only if a global Promise object is available like in all modern browsers | | |
| 413 | * or through a polyfill). | | |
| 414 | * | | |
| 415 | * Options: | | |
| 416 | * useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`. | | |
| 417 | * copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value | | |
| 418 | * is `true`. | | |
| 419 | * makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by | | |
| 420 | * property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a | | |
| 421 | * running number which increases with each injection. This is done to avoid duplicate IDs in the DOM. | | |
| 422 | * beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns | | |
| 423 | * a string it is used as the URL instead of the `img` element's `src` attribute. | | |
| 424 | * afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a | | |
| 425 | * parameters. If caching is active this hook will only get called once for injected SVGs with the | | |
| 426 | * same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs | | |
| 427 | * with the same absolute path. It's also possible to return an `svg` string or `svg` element which | | |
| 428 | * will then be used for the injection. | | |
| 429 | * beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If | | |
| 430 | * any html element is returned it gets injected instead of applying the default SVG injection. | | |
| 431 | * afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters. | | |
| 432 | * onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or | | |
| 433 | * failed to inject. | | |
| 434 | * onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter. | | |
| 435 | * The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG), | | |
| 436 | * `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed). | | |
| 437 | * | | |
| 438 | * @param {HTMLImageElement} img - an img element or an array of img elements | | |
| 439 | * @param {Object} [options] - optional parameter with [options](#options) for this injection. | | |
| 440 | */ | | |
| 441 | function SVGInject(img, options) { | | |
| 442 | options = mergeOptions(defaultOptions, options); | | |
| 443 | | | |
| 444 | var run = function(resolve) { | | |
| 445 | var allFinish = function() { | | |
| 446 | var onAllFinish = options.onAllFinish; | | |
| 447 | if (onAllFinish) { | | |
| 448 | onAllFinish(); | | |
| 449 | } | | |
| 450 | resolve && resolve(); | | |
| 451 | }; | | |
| 452 | | | |
| 453 | if (img && typeof img[_LENGTH_] != _UNDEFINED_) { | | |
| 454 | // an array like structure of img elements | | |
| 455 | var injectIndex = 0; | | |
| 456 | var injectCount = img[_LENGTH_]; | | |
| 457 | | | |
| 458 | if (injectCount == 0) { | | |
| 459 | allFinish(); | | |
| 460 | } else { | | |
| 461 | var finish = function() { | | |
| 462 | if (++injectIndex == injectCount) { | | |
| 463 | allFinish(); | | |
| 464 | } | | |
| 465 | }; | | |
| 466 | | | |
| 467 | for (var i = 0; i < injectCount; i++) { | | |
| 468 | SVGInjectElement(img[i], options, finish); | | |
| 469 | } | | |
| 470 | } | | |
| 471 | } else { | | |
| 472 | // only one img element | | |
| 473 | SVGInjectElement(img, options, allFinish); | | |
| 474 | } | | |
| 475 | }; | | |
| 476 | | | |
| 477 | // return a Promise object if globally available | | |
| 478 | return typeof Promise == _UNDEFINED_ ? run() : new Promise(run); | | |
| 479 | } | | |
| 480 | | | |
| 481 | | | |
| 482 | // Injects a single svg element. Options must be already merged with the default options. | | |
| 483 | function SVGInjectElement(imgElem, options, callback) { | | |
| 484 | if (imgElem) { | | |
| 485 | var svgInjectAttributeValue = imgElem[__SVGINJECT]; | | |
| 486 | if (!svgInjectAttributeValue) { | | |
| 487 | removeEventListeners(imgElem); | | |
| 488 | | | |
| 489 | if (!IS_SVG_SUPPORTED) { | | |
| 490 | svgNotSupported(imgElem, options); | | |
| 491 | callback(); | | |
| 492 | return; | | |
| 493 | } | | |
| 494 | // Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load | | |
| 495 | // URL path. Else use the imgElem's src attribute value. | | |
| 496 | var beforeLoad = options.beforeLoad; | | |
| 497 | var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src'); | | |
| 498 | | | |
| 499 | if (!src) { | | |
| 500 | // If no image src attribute is set do no injection. This can only be reached by using javascript | | |
| 501 | // because if no src attribute is set the onload and onerror events do not get called | | |
| 502 | if (src === '') { | | |
| 503 | loadFail(imgElem, options); | | |
| 504 | } | | |
| 505 | callback(); | | |
| 506 | return; | | |
| 507 | } | | |
| 508 | | | |
| 509 | // set array so later calls can register callbacks | | |
| 510 | var onFinishCallbacks = []; | | |
| 511 | imgElem[__SVGINJECT] = onFinishCallbacks; | | |
| 512 | | | |
| 513 | var onFinish = function() { | | |
| 514 | callback(); | | |
| 515 | onFinishCallbacks.forEach(function(onFinishCallback) { | | |
| 516 | onFinishCallback(); | | |
| 517 | }); | | |
| 518 | }; | | |
| 519 | | | |
| 520 | var absUrl = getAbsoluteUrl(src); | | |
| 521 | var useCacheOption = options.useCache; | | |
| 522 | var makeIdsUniqueOption = options.makeIdsUnique; | | |
| 523 | | | |
| 524 | var setSvgLoadCacheValue = function(val) { | | |
| 525 | if (useCacheOption) { | | |
| 526 | svgLoadCache[absUrl].forEach(function(svgLoad) { | | |
| 527 | svgLoad(val); | | |
| 528 | }); | | |
| 529 | svgLoadCache[absUrl] = val; | | |
| 530 | } | | |
| 531 | }; | | |
| 532 | | | |
| 533 | if (useCacheOption) { | | |
| 534 | var svgLoad = svgLoadCache[absUrl]; | | |
| 535 | | | |
| 536 | var handleLoadValue = function(loadValue) { | | |
| 537 | if (loadValue === LOAD_FAIL) { | | |
| 538 | loadFail(imgElem, options); | | |
| 539 | } else if (loadValue === SVG_INVALID) { | | |
| 540 | svgInvalid(imgElem, options); | | |
| 541 | } else { | | |
| 542 | var hasUniqueIds = loadValue[0]; | | |
| 543 | var svgString = loadValue[1]; | | |
| 544 | var uniqueIdsSvgString = loadValue[2]; | | |
| 545 | var svgElem; | | |
| 546 | | | |
| 547 | if (makeIdsUniqueOption) { | | |
| 548 | if (hasUniqueIds === NULL) { | | |
| 549 | // IDs for the SVG string have not been made unique before. This may happen if previous | | |
| 550 | // injection of a cached SVG have been run with the option makedIdsUnique set to false | | |
| 551 | svgElem = buildSvgElement(svgString, false); | | |
| 552 | hasUniqueIds = makeIdsUnique(svgElem, false); | | |
| 553 | | | |
| 554 | loadValue[0] = hasUniqueIds; | | |
| 555 | loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem); | | |
| 556 | } else if (hasUniqueIds) { | | |
| 557 | // Make IDs unique for already cached SVGs with better performance | | |
| 558 | svgString = makeIdsUniqueCached(uniqueIdsSvgString); | | |
| 559 | } | | |
| 560 | } | | |
| 561 | | | |
| 562 | svgElem = svgElem || buildSvgElement(svgString, false); | | |
| 563 | | | |
| 564 | inject(imgElem, svgElem, absUrl, options); | | |
| 565 | } | | |
| 566 | onFinish(); | | |
| 567 | }; | | |
| 568 | | | |
| 569 | if (typeof svgLoad != _UNDEFINED_) { | | |
| 570 | // Value for url exists in cache | | |
| 571 | if (svgLoad.isCallbackQueue) { | | |
| 572 | // Same url has been cached, but value has not been loaded yet, so add to callbacks | | |
| 573 | svgLoad.push(handleLoadValue); | | |
| 574 | } else { | | |
| 575 | handleLoadValue(svgLoad); | | |
| 576 | } | | |
| 577 | return; | | |
| 578 | } else { | | |
| 579 | var svgLoad = []; | | |
| 580 | // set property isCallbackQueue to Array to differentiate from array with cached loaded values | | |
| 581 | svgLoad.isCallbackQueue = true; | | |
| 582 | svgLoadCache[absUrl] = svgLoad; | | |
| 583 | } | | |
| 584 | } | | |
| 585 | | | |
| 586 | // Load the SVG because it is not cached or caching is disabled | | |
| 587 | loadSvg(absUrl, function(svgXml, svgString) { | | |
| 588 | // Use the XML from the XHR request if it is an instance of Document. Otherwise | | |
| 589 | // (for example of IE9), create the svg document from the svg string. | | |
| 590 | var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true); | | |
| 591 | | | |
| 592 | var afterLoad = options.afterLoad; | | |
| 593 | if (afterLoad) { | | |
| 594 | // Invoke afterLoad hook which may modify the SVG element. After load may also return a new | | |
| 595 | // svg element or svg string | | |
| 596 | var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem; | | |
| 597 | if (svgElemOrSvgString) { | | |
| 598 | // Update svgElem and svgString because of modifications to the SVG element or SVG string in | | |
| 599 | // the afterLoad hook, so the modified SVG is also used for all later cached injections | | |
| 600 | var isString = typeof svgElemOrSvgString == 'string'; | | |
| 601 | svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem); | | |
| 602 | svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString; | | |
| 603 | } | | |
| 604 | } | | |
| 605 | | | |
| 606 | if (svgElem instanceof SVGElement) { | | |
| 607 | var hasUniqueIds = NULL; | | |
| 608 | if (makeIdsUniqueOption) { | | |
| 609 | hasUniqueIds = makeIdsUnique(svgElem, false); | | |
| 610 | } | | |
| 611 | | | |
| 612 | if (useCacheOption) { | | |
| 613 | var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem); | | |
| 614 | // set an array with three entries to the load cache | | |
| 615 | setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]); | | |
| 616 | } | | |
| 617 | | | |
| 618 | inject(imgElem, svgElem, absUrl, options); | | |
| 619 | } else { | | |
| 620 | svgInvalid(imgElem, options); | | |
| 621 | setSvgLoadCacheValue(SVG_INVALID); | | |
| 622 | } | | |
| 623 | onFinish(); | | |
| 624 | }, function() { | | |
| 625 | loadFail(imgElem, options); | | |
| 626 | setSvgLoadCacheValue(LOAD_FAIL); | | |
| 627 | onFinish(); | | |
| 628 | }); | | |
| 629 | } else { | | |
| 630 | if (Array.isArray(svgInjectAttributeValue)) { | | |
| 631 | // svgInjectAttributeValue is an array. Injection is not complete so register callback | | |
| 632 | svgInjectAttributeValue.push(callback); | | |
| 633 | } else { | | |
| 634 | callback(); | | |
| 635 | } | | |
| 636 | } | | |
| 637 | } else { | | |
| 638 | imgNotSet(); | | |
| 639 | } | | |
| 640 | } | | |
| 641 | | | |
| 642 | | | |
| 643 | /** | | |
| 644 | * Sets the default [options](#options) for SVGInject. | | |
| 645 | * | | |
| 646 | * @param {Object} [options] - default [options](#options) for an injection. | | |
| 647 | */ | | |
| 648 | SVGInject.setOptions = function(options) { | | |
| 649 | defaultOptions = mergeOptions(defaultOptions, options); | | |
| 650 | }; | | |
| 651 | | | |
| 652 | | | |
| 653 | // Create a new instance of SVGInject | | |
| 654 | SVGInject.create = createSVGInject; | | |
| 655 | | | |
| 656 | | | |
| 657 | /** | | |
| 658 | * Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails | | |
| 659 | * (for example if file is not found or if the browser does not support SVG). This triggers a call to the | | |
| 660 | * options onFail hook if available. The optional second parameter will be set as the new src attribute | | |
| 661 | * for the img element. | | |
| 662 | * | | |
| 663 | * @param {HTMLImageElement} img - an img element | | |
| 664 | * @param {String} [fallbackSrc] - optional parameter fallback src | | |
| 665 | */ | | |
| 666 | SVGInject.err = function(img, fallbackSrc) { | | |
| 667 | if (img) { | | |
| 668 | if (img[__SVGINJECT] != FAIL) { | | |
| 669 | removeEventListeners(img); | | |
| 670 | | | |
| 671 | if (!IS_SVG_SUPPORTED) { | | |
| 672 | svgNotSupported(img, defaultOptions); | | |
| 673 | } else { | | |
| 674 | removeOnLoadAttribute(img); | | |
| 675 | loadFail(img, defaultOptions); | | |
| 676 | } | | |
| 677 | if (fallbackSrc) { | | |
| 678 | removeOnLoadAttribute(img); | | |
| 679 | img.src = fallbackSrc; | | |
| 680 | } | | |
| 681 | } | | |
| 682 | } else { | | |
| 683 | imgNotSet(); | | |
| 684 | } | | |
| 685 | }; | | |
| 686 | | | |
| 687 | window[globalName] = SVGInject; | | |
| 688 | | | |
| 689 | return SVGInject; | | |
| 690 | } | | |
| 691 | | | |
| 692 | var SVGInjectInstance = createSVGInject('SVGInject'); | | |
| 693 | | | |
| 694 | if (typeof module == 'object' && typeof module.exports == 'object') { | | |
| 695 | module.exports = SVGInjectInstance; | | |
| 696 | } | | |
| 697 | })(window, document); | | |
| 697 |
\ No newline at end of file | |
\ No newline at end of file |