697 lines
26 KiB
JavaScript
697 lines
26 KiB
JavaScript
|
/**
|
||
|
* SVGInject - Version 1.2.3
|
||
|
* A tiny, intuitive, robust, caching solution for injecting SVG files inline into the DOM.
|
||
|
*
|
||
|
* https://github.com/iconfu/svg-inject
|
||
|
*
|
||
|
* Copyright (c) 2018 INCORS, the creators of iconfu.com
|
||
|
* @license MIT License - https://github.com/iconfu/svg-inject/blob/master/LICENSE
|
||
|
*/
|
||
|
|
||
|
(function(window, document) {
|
||
|
// constants for better minification
|
||
|
var _CREATE_ELEMENT_ = 'createElement';
|
||
|
var _GET_ELEMENTS_BY_TAG_NAME_ = 'getElementsByTagName';
|
||
|
var _LENGTH_ = 'length';
|
||
|
var _STYLE_ = 'style';
|
||
|
var _TITLE_ = 'title';
|
||
|
var _UNDEFINED_ = 'undefined';
|
||
|
var _SET_ATTRIBUTE_ = 'setAttribute';
|
||
|
var _GET_ATTRIBUTE_ = 'getAttribute';
|
||
|
|
||
|
var NULL = null;
|
||
|
|
||
|
// constants
|
||
|
var __SVGINJECT = '__svgInject';
|
||
|
var ID_SUFFIX = '--inject-';
|
||
|
var ID_SUFFIX_REGEX = new RegExp(ID_SUFFIX + '\\d+', "g");
|
||
|
var LOAD_FAIL = 'LOAD_FAIL';
|
||
|
var SVG_NOT_SUPPORTED = 'SVG_NOT_SUPPORTED';
|
||
|
var SVG_INVALID = 'SVG_INVALID';
|
||
|
var ATTRIBUTE_EXCLUSION_NAMES = ['src', 'alt', 'onload', 'onerror'];
|
||
|
var A_ELEMENT = document[_CREATE_ELEMENT_]('a');
|
||
|
var IS_SVG_SUPPORTED = typeof SVGRect != _UNDEFINED_;
|
||
|
var DEFAULT_OPTIONS = {
|
||
|
useCache: true,
|
||
|
copyAttributes: true,
|
||
|
makeIdsUnique: true
|
||
|
};
|
||
|
// Map of IRI referenceable tag names to properties that can reference them. This is defined in
|
||
|
// https://www.w3.org/TR/SVG11/linking.html#processingIRI
|
||
|
var IRI_TAG_PROPERTIES_MAP = {
|
||
|
clipPath: ['clip-path'],
|
||
|
'color-profile': NULL,
|
||
|
cursor: NULL,
|
||
|
filter: NULL,
|
||
|
linearGradient: ['fill', 'stroke'],
|
||
|
marker: ['marker', 'marker-end', 'marker-mid', 'marker-start'],
|
||
|
mask: NULL,
|
||
|
pattern: ['fill', 'stroke'],
|
||
|
radialGradient: ['fill', 'stroke']
|
||
|
};
|
||
|
var INJECTED = 1;
|
||
|
var FAIL = 2;
|
||
|
|
||
|
var uniqueIdCounter = 1;
|
||
|
var xmlSerializer;
|
||
|
var domParser;
|
||
|
|
||
|
|
||
|
// creates an SVG document from an SVG string
|
||
|
function svgStringToSvgDoc(svgStr) {
|
||
|
domParser = domParser || new DOMParser();
|
||
|
return domParser.parseFromString(svgStr, 'text/xml');
|
||
|
}
|
||
|
|
||
|
|
||
|
// searializes an SVG element to an SVG string
|
||
|
function svgElemToSvgString(svgElement) {
|
||
|
xmlSerializer = xmlSerializer || new XMLSerializer();
|
||
|
return xmlSerializer.serializeToString(svgElement);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Returns the absolute url for the specified url
|
||
|
function getAbsoluteUrl(url) {
|
||
|
A_ELEMENT.href = url;
|
||
|
return A_ELEMENT.href;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Load svg with an XHR request
|
||
|
function loadSvg(url, callback, errorCallback) {
|
||
|
if (url) {
|
||
|
var req = new XMLHttpRequest();
|
||
|
req.onreadystatechange = function() {
|
||
|
if (req.readyState == 4) {
|
||
|
// readyState is DONE
|
||
|
var status = req.status;
|
||
|
if (status == 200) {
|
||
|
// request status is OK
|
||
|
callback(req.responseXML, req.responseText.trim());
|
||
|
} else if (status >= 400) {
|
||
|
// request status is error (4xx or 5xx)
|
||
|
errorCallback();
|
||
|
} else if (status == 0) {
|
||
|
// request status 0 can indicate a failed cross-domain call
|
||
|
errorCallback();
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
req.open('GET', url, true);
|
||
|
req.send();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Copy attributes from img element to svg element
|
||
|
function copyAttributes(imgElem, svgElem) {
|
||
|
var attribute;
|
||
|
var attributeName;
|
||
|
var attributeValue;
|
||
|
var attributes = imgElem.attributes;
|
||
|
for (var i = 0; i < attributes[_LENGTH_]; i++) {
|
||
|
attribute = attributes[i];
|
||
|
attributeName = attribute.name;
|
||
|
// Only copy attributes not explicitly excluded from copying
|
||
|
if (ATTRIBUTE_EXCLUSION_NAMES.indexOf(attributeName) == -1) {
|
||
|
attributeValue = attribute.value;
|
||
|
// If img attribute is "title", insert a title element into SVG element
|
||
|
if (attributeName == _TITLE_) {
|
||
|
var titleElem;
|
||
|
var firstElementChild = svgElem.firstElementChild;
|
||
|
if (firstElementChild && firstElementChild.localName.toLowerCase() == _TITLE_) {
|
||
|
// If the SVG element's first child is a title element, keep it as the title element
|
||
|
titleElem = firstElementChild;
|
||
|
} else {
|
||
|
// If the SVG element's first child element is not a title element, create a new title
|
||
|
// ele,emt and set it as the first child
|
||
|
titleElem = document[_CREATE_ELEMENT_ + 'NS']('http://www.w3.org/2000/svg', _TITLE_);
|
||
|
svgElem.insertBefore(titleElem, firstElementChild);
|
||
|
}
|
||
|
// Set new title content
|
||
|
titleElem.textContent = attributeValue;
|
||
|
} else {
|
||
|
// Set img attribute to svg element
|
||
|
svgElem[_SET_ATTRIBUTE_](attributeName, attributeValue);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// This function appends a suffix to IDs of referenced elements in the <defs> in order to to avoid ID collision
|
||
|
// between multiple injected SVGs. The suffix has the form "--inject-X", where X is a running number which is
|
||
|
// incremented with each injection. References to the IDs are adjusted accordingly.
|
||
|
// We assume tha all IDs within the injected SVG are unique, therefore the same suffix can be used for all IDs of one
|
||
|
// injected SVG.
|
||
|
// If the onlyReferenced argument is set to true, only those IDs will be made unique that are referenced from within the SVG
|
||
|
function makeIdsUnique(svgElem, onlyReferenced) {
|
||
|
var idSuffix = ID_SUFFIX + uniqueIdCounter++;
|
||
|
// Regular expression for functional notations of an IRI references. This will find occurences in the form
|
||
|
// url(#anyId) or url("#anyId") (for Internet Explorer) and capture the referenced ID
|
||
|
var funcIriRegex = /url\("?#([a-zA-Z][\w:.-]*)"?\)/g;
|
||
|
// Get all elements with an ID. The SVG spec recommends to put referenced elements inside <defs> elements, but
|
||
|
// this is not a requirement, therefore we have to search for IDs in the whole SVG.
|
||
|
var idElements = svgElem.querySelectorAll('[id]');
|
||
|
var idElem;
|
||
|
// An object containing referenced IDs as keys is used if only referenced IDs should be uniquified.
|
||
|
// If this object does not exist, all IDs will be uniquified.
|
||
|
var referencedIds = onlyReferenced ? [] : NULL;
|
||
|
var tagName;
|
||
|
var iriTagNames = {};
|
||
|
var iriProperties = [];
|
||
|
var changed = false;
|
||
|
var i, j;
|
||
|
|
||
|
if (idElements[_LENGTH_]) {
|
||
|
// Make all IDs unique by adding the ID suffix and collect all encountered tag names
|
||
|
// that are IRI referenceable from properities.
|
||
|
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||
|
tagName = idElements[i].localName; // Use non-namespaced tag name
|
||
|
// Make ID unique if tag name is IRI referenceable
|
||
|
if (tagName in IRI_TAG_PROPERTIES_MAP) {
|
||
|
iriTagNames[tagName] = 1;
|
||
|
}
|
||
|
}
|
||
|
// Get all properties that are mapped to the found IRI referenceable tags
|
||
|
for (tagName in iriTagNames) {
|
||
|
(IRI_TAG_PROPERTIES_MAP[tagName] || [tagName]).forEach(function (mappedProperty) {
|
||
|
// Add mapped properties to array of iri referencing properties.
|
||
|
// Use linear search here because the number of possible entries is very small (maximum 11)
|
||
|
if (iriProperties.indexOf(mappedProperty) < 0) {
|
||
|
iriProperties.push(mappedProperty);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
if (iriProperties[_LENGTH_]) {
|
||
|
// Add "style" to properties, because it may contain references in the form 'style="fill:url(#myFill)"'
|
||
|
iriProperties.push(_STYLE_);
|
||
|
}
|
||
|
// Run through all elements of the SVG and replace IDs in references.
|
||
|
// To get all descending elements, getElementsByTagName('*') seems to perform faster than querySelectorAll('*').
|
||
|
// Since svgElem.getElementsByTagName('*') does not return the svg element itself, we have to handle it separately.
|
||
|
var descElements = svgElem[_GET_ELEMENTS_BY_TAG_NAME_]('*');
|
||
|
var element = svgElem;
|
||
|
var propertyName;
|
||
|
var value;
|
||
|
var newValue;
|
||
|
for (i = -1; element != NULL;) {
|
||
|
if (element.localName == _STYLE_) {
|
||
|
// If element is a style element, replace IDs in all occurences of "url(#anyId)" in text content
|
||
|
value = element.textContent;
|
||
|
newValue = value && value.replace(funcIriRegex, function(match, id) {
|
||
|
if (referencedIds) {
|
||
|
referencedIds[id] = 1;
|
||
|
}
|
||
|
return 'url(#' + id + idSuffix + ')';
|
||
|
});
|
||
|
if (newValue !== value) {
|
||
|
element.textContent = newValue;
|
||
|
}
|
||
|
} else if (element.hasAttributes()) {
|
||
|
// Run through all property names for which IDs were found
|
||
|
for (j = 0; j < iriProperties[_LENGTH_]; j++) {
|
||
|
propertyName = iriProperties[j];
|
||
|
value = element[_GET_ATTRIBUTE_](propertyName);
|
||
|
newValue = value && value.replace(funcIriRegex, function(match, id) {
|
||
|
if (referencedIds) {
|
||
|
referencedIds[id] = 1;
|
||
|
}
|
||
|
return 'url(#' + id + idSuffix + ')';
|
||
|
});
|
||
|
if (newValue !== value) {
|
||
|
element[_SET_ATTRIBUTE_](propertyName, newValue);
|
||
|
}
|
||
|
}
|
||
|
// Replace IDs in xlink:ref and href attributes
|
||
|
['xlink:href', 'href'].forEach(function(refAttrName) {
|
||
|
var iri = element[_GET_ATTRIBUTE_](refAttrName);
|
||
|
if (/^\s*#/.test(iri)) { // Check if iri is non-null and internal reference
|
||
|
iri = iri.trim();
|
||
|
element[_SET_ATTRIBUTE_](refAttrName, iri + idSuffix);
|
||
|
if (referencedIds) {
|
||
|
// Add ID to referenced IDs
|
||
|
referencedIds[iri.substring(1)] = 1;
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
element = descElements[++i];
|
||
|
}
|
||
|
for (i = 0; i < idElements[_LENGTH_]; i++) {
|
||
|
idElem = idElements[i];
|
||
|
// If set of referenced IDs exists, make only referenced IDs unique,
|
||
|
// otherwise make all IDs unique.
|
||
|
if (!referencedIds || referencedIds[idElem.id]) {
|
||
|
// Add suffix to element's ID
|
||
|
idElem.id += idSuffix;
|
||
|
changed = true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// return true if SVG element has changed
|
||
|
return changed;
|
||
|
}
|
||
|
|
||
|
|
||
|
// For cached SVGs the IDs are made unique by simply replacing the already inserted unique IDs with a
|
||
|
// higher ID counter. This is much more performant than a call to makeIdsUnique().
|
||
|
function makeIdsUniqueCached(svgString) {
|
||
|
return svgString.replace(ID_SUFFIX_REGEX, ID_SUFFIX + uniqueIdCounter++);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Inject SVG by replacing the img element with the SVG element in the DOM
|
||
|
function inject(imgElem, svgElem, absUrl, options) {
|
||
|
if (svgElem) {
|
||
|
svgElem[_SET_ATTRIBUTE_]('data-inject-url', absUrl);
|
||
|
var parentNode = imgElem.parentNode;
|
||
|
if (parentNode) {
|
||
|
if (options.copyAttributes) {
|
||
|
copyAttributes(imgElem, svgElem);
|
||
|
}
|
||
|
// Invoke beforeInject hook if set
|
||
|
var beforeInject = options.beforeInject;
|
||
|
var injectElem = (beforeInject && beforeInject(imgElem, svgElem)) || svgElem;
|
||
|
// Replace img element with new element. This is the actual injection.
|
||
|
parentNode.replaceChild(injectElem, imgElem);
|
||
|
// Mark img element as injected
|
||
|
imgElem[__SVGINJECT] = INJECTED;
|
||
|
removeOnLoadAttribute(imgElem);
|
||
|
// Invoke afterInject hook if set
|
||
|
var afterInject = options.afterInject;
|
||
|
if (afterInject) {
|
||
|
afterInject(imgElem, injectElem);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
svgInvalid(imgElem, options);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Merges any number of options objects into a new object
|
||
|
function mergeOptions() {
|
||
|
var mergedOptions = {};
|
||
|
var args = arguments;
|
||
|
// Iterate over all specified options objects and add all properties to the new options object
|
||
|
for (var i = 0; i < args[_LENGTH_]; i++) {
|
||
|
var argument = args[i];
|
||
|
for (var key in argument) {
|
||
|
if (argument.hasOwnProperty(key)) {
|
||
|
mergedOptions[key] = argument[key];
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return mergedOptions;
|
||
|
}
|
||
|
|
||
|
|
||
|
// Adds the specified CSS to the document's <head> element
|
||
|
function addStyleToHead(css) {
|
||
|
var head = document[_GET_ELEMENTS_BY_TAG_NAME_]('head')[0];
|
||
|
if (head) {
|
||
|
var style = document[_CREATE_ELEMENT_](_STYLE_);
|
||
|
style.type = 'text/css';
|
||
|
style.appendChild(document.createTextNode(css));
|
||
|
head.appendChild(style);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
// Builds an SVG element from the specified SVG string
|
||
|
function buildSvgElement(svgStr, verify) {
|
||
|
if (verify) {
|
||
|
var svgDoc;
|
||
|
try {
|
||
|
// Parse the SVG string with DOMParser
|
||
|
svgDoc = svgStringToSvgDoc(svgStr);
|
||
|
} catch(e) {
|
||
|
return NULL;
|
||
|
}
|
||
|
if (svgDoc[_GET_ELEMENTS_BY_TAG_NAME_]('parsererror')[_LENGTH_]) {
|
||
|
// DOMParser does not throw an exception, but instead puts parsererror tags in the document
|
||
|
return NULL;
|
||
|
}
|
||
|
return svgDoc.documentElement;
|
||
|
} else {
|
||
|
var div = document.createElement('div');
|
||
|
div.innerHTML = svgStr;
|
||
|
return div.firstElementChild;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
function removeOnLoadAttribute(imgElem) {
|
||
|
// Remove the onload attribute. Should only be used to remove the unstyled image flash protection and
|
||
|
// make the element visible, not for removing the event listener.
|
||
|
imgElem.removeAttribute('onload');
|
||
|
}
|
||
|
|
||
|
|
||
|
function errorMessage(msg) {
|
||
|
console.error('SVGInject: ' + msg);
|
||
|
}
|
||
|
|
||
|
|
||
|
function fail(imgElem, status, options) {
|
||
|
imgElem[__SVGINJECT] = FAIL;
|
||
|
if (options.onFail) {
|
||
|
options.onFail(imgElem, status);
|
||
|
} else {
|
||
|
errorMessage(status);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
function svgInvalid(imgElem, options) {
|
||
|
removeOnLoadAttribute(imgElem);
|
||
|
fail(imgElem, SVG_INVALID, options);
|
||
|
}
|
||
|
|
||
|
|
||
|
function svgNotSupported(imgElem, options) {
|
||
|
removeOnLoadAttribute(imgElem);
|
||
|
fail(imgElem, SVG_NOT_SUPPORTED, options);
|
||
|
}
|
||
|
|
||
|
|
||
|
function loadFail(imgElem, options) {
|
||
|
fail(imgElem, LOAD_FAIL, options);
|
||
|
}
|
||
|
|
||
|
|
||
|
function removeEventListeners(imgElem) {
|
||
|
imgElem.onload = NULL;
|
||
|
imgElem.onerror = NULL;
|
||
|
}
|
||
|
|
||
|
|
||
|
function imgNotSet(msg) {
|
||
|
errorMessage('no img element');
|
||
|
}
|
||
|
|
||
|
|
||
|
function createSVGInject(globalName, options) {
|
||
|
var defaultOptions = mergeOptions(DEFAULT_OPTIONS, options);
|
||
|
var svgLoadCache = {};
|
||
|
|
||
|
if (IS_SVG_SUPPORTED) {
|
||
|
// If the browser supports SVG, add a small stylesheet that hides the <img> elements until
|
||
|
// injection is finished. This avoids showing the unstyled SVGs before style is applied.
|
||
|
addStyleToHead('img[onload^="' + globalName + '("]{visibility:hidden;}');
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* SVGInject
|
||
|
*
|
||
|
* Injects the SVG specified in the `src` attribute of the specified `img` element or array of `img`
|
||
|
* elements. Returns a Promise object which resolves if all passed in `img` elements have either been
|
||
|
* injected or failed to inject (Only if a global Promise object is available like in all modern browsers
|
||
|
* or through a polyfill).
|
||
|
*
|
||
|
* Options:
|
||
|
* useCache: If set to `true` the SVG will be cached using the absolute URL. Default value is `true`.
|
||
|
* copyAttributes: If set to `true` the attributes will be copied from `img` to `svg`. Dfault value
|
||
|
* is `true`.
|
||
|
* makeIdsUnique: If set to `true` the ID of elements in the `<defs>` element that can be references by
|
||
|
* property values (for example 'clipPath') are made unique by appending "--inject-X", where X is a
|
||
|
* running number which increases with each injection. This is done to avoid duplicate IDs in the DOM.
|
||
|
* beforeLoad: Hook before SVG is loaded. The `img` element is passed as a parameter. If the hook returns
|
||
|
* a string it is used as the URL instead of the `img` element's `src` attribute.
|
||
|
* afterLoad: Hook after SVG is loaded. The loaded `svg` element and `svg` string are passed as a
|
||
|
* parameters. If caching is active this hook will only get called once for injected SVGs with the
|
||
|
* same absolute path. Changes to the `svg` element in this hook will be applied to all injected SVGs
|
||
|
* with the same absolute path. It's also possible to return an `svg` string or `svg` element which
|
||
|
* will then be used for the injection.
|
||
|
* beforeInject: Hook before SVG is injected. The `img` and `svg` elements are passed as parameters. If
|
||
|
* any html element is returned it gets injected instead of applying the default SVG injection.
|
||
|
* afterInject: Hook after SVG is injected. The `img` and `svg` elements are passed as parameters.
|
||
|
* onAllFinish: Hook after all `img` elements passed to an SVGInject() call have either been injected or
|
||
|
* failed to inject.
|
||
|
* onFail: Hook after injection fails. The `img` element and a `status` string are passed as an parameter.
|
||
|
* The `status` can be either `'SVG_NOT_SUPPORTED'` (the browser does not support SVG),
|
||
|
* `'SVG_INVALID'` (the SVG is not in a valid format) or `'LOAD_FAILED'` (loading of the SVG failed).
|
||
|
*
|
||
|
* @param {HTMLImageElement} img - an img element or an array of img elements
|
||
|
* @param {Object} [options] - optional parameter with [options](#options) for this injection.
|
||
|
*/
|
||
|
function SVGInject(img, options) {
|
||
|
options = mergeOptions(defaultOptions, options);
|
||
|
|
||
|
var run = function(resolve) {
|
||
|
var allFinish = function() {
|
||
|
var onAllFinish = options.onAllFinish;
|
||
|
if (onAllFinish) {
|
||
|
onAllFinish();
|
||
|
}
|
||
|
resolve && resolve();
|
||
|
};
|
||
|
|
||
|
if (img && typeof img[_LENGTH_] != _UNDEFINED_) {
|
||
|
// an array like structure of img elements
|
||
|
var injectIndex = 0;
|
||
|
var injectCount = img[_LENGTH_];
|
||
|
|
||
|
if (injectCount == 0) {
|
||
|
allFinish();
|
||
|
} else {
|
||
|
var finish = function() {
|
||
|
if (++injectIndex == injectCount) {
|
||
|
allFinish();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
for (var i = 0; i < injectCount; i++) {
|
||
|
SVGInjectElement(img[i], options, finish);
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// only one img element
|
||
|
SVGInjectElement(img, options, allFinish);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// return a Promise object if globally available
|
||
|
return typeof Promise == _UNDEFINED_ ? run() : new Promise(run);
|
||
|
}
|
||
|
|
||
|
|
||
|
// Injects a single svg element. Options must be already merged with the default options.
|
||
|
function SVGInjectElement(imgElem, options, callback) {
|
||
|
if (imgElem) {
|
||
|
var svgInjectAttributeValue = imgElem[__SVGINJECT];
|
||
|
if (!svgInjectAttributeValue) {
|
||
|
removeEventListeners(imgElem);
|
||
|
|
||
|
if (!IS_SVG_SUPPORTED) {
|
||
|
svgNotSupported(imgElem, options);
|
||
|
callback();
|
||
|
return;
|
||
|
}
|
||
|
// Invoke beforeLoad hook if set. If the beforeLoad returns a value use it as the src for the load
|
||
|
// URL path. Else use the imgElem's src attribute value.
|
||
|
var beforeLoad = options.beforeLoad;
|
||
|
var src = (beforeLoad && beforeLoad(imgElem)) || imgElem[_GET_ATTRIBUTE_]('src');
|
||
|
|
||
|
if (!src) {
|
||
|
// If no image src attribute is set do no injection. This can only be reached by using javascript
|
||
|
// because if no src attribute is set the onload and onerror events do not get called
|
||
|
if (src === '') {
|
||
|
loadFail(imgElem, options);
|
||
|
}
|
||
|
callback();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// set array so later calls can register callbacks
|
||
|
var onFinishCallbacks = [];
|
||
|
imgElem[__SVGINJECT] = onFinishCallbacks;
|
||
|
|
||
|
var onFinish = function() {
|
||
|
callback();
|
||
|
onFinishCallbacks.forEach(function(onFinishCallback) {
|
||
|
onFinishCallback();
|
||
|
});
|
||
|
};
|
||
|
|
||
|
var absUrl = getAbsoluteUrl(src);
|
||
|
var useCacheOption = options.useCache;
|
||
|
var makeIdsUniqueOption = options.makeIdsUnique;
|
||
|
|
||
|
var setSvgLoadCacheValue = function(val) {
|
||
|
if (useCacheOption) {
|
||
|
svgLoadCache[absUrl].forEach(function(svgLoad) {
|
||
|
svgLoad(val);
|
||
|
});
|
||
|
svgLoadCache[absUrl] = val;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
if (useCacheOption) {
|
||
|
var svgLoad = svgLoadCache[absUrl];
|
||
|
|
||
|
var handleLoadValue = function(loadValue) {
|
||
|
if (loadValue === LOAD_FAIL) {
|
||
|
loadFail(imgElem, options);
|
||
|
} else if (loadValue === SVG_INVALID) {
|
||
|
svgInvalid(imgElem, options);
|
||
|
} else {
|
||
|
var hasUniqueIds = loadValue[0];
|
||
|
var svgString = loadValue[1];
|
||
|
var uniqueIdsSvgString = loadValue[2];
|
||
|
var svgElem;
|
||
|
|
||
|
if (makeIdsUniqueOption) {
|
||
|
if (hasUniqueIds === NULL) {
|
||
|
// IDs for the SVG string have not been made unique before. This may happen if previous
|
||
|
// injection of a cached SVG have been run with the option makedIdsUnique set to false
|
||
|
svgElem = buildSvgElement(svgString, false);
|
||
|
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||
|
|
||
|
loadValue[0] = hasUniqueIds;
|
||
|
loadValue[2] = hasUniqueIds && svgElemToSvgString(svgElem);
|
||
|
} else if (hasUniqueIds) {
|
||
|
// Make IDs unique for already cached SVGs with better performance
|
||
|
svgString = makeIdsUniqueCached(uniqueIdsSvgString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
svgElem = svgElem || buildSvgElement(svgString, false);
|
||
|
|
||
|
inject(imgElem, svgElem, absUrl, options);
|
||
|
}
|
||
|
onFinish();
|
||
|
};
|
||
|
|
||
|
if (typeof svgLoad != _UNDEFINED_) {
|
||
|
// Value for url exists in cache
|
||
|
if (svgLoad.isCallbackQueue) {
|
||
|
// Same url has been cached, but value has not been loaded yet, so add to callbacks
|
||
|
svgLoad.push(handleLoadValue);
|
||
|
} else {
|
||
|
handleLoadValue(svgLoad);
|
||
|
}
|
||
|
return;
|
||
|
} else {
|
||
|
var svgLoad = [];
|
||
|
// set property isCallbackQueue to Array to differentiate from array with cached loaded values
|
||
|
svgLoad.isCallbackQueue = true;
|
||
|
svgLoadCache[absUrl] = svgLoad;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Load the SVG because it is not cached or caching is disabled
|
||
|
loadSvg(absUrl, function(svgXml, svgString) {
|
||
|
// Use the XML from the XHR request if it is an instance of Document. Otherwise
|
||
|
// (for example of IE9), create the svg document from the svg string.
|
||
|
var svgElem = svgXml instanceof Document ? svgXml.documentElement : buildSvgElement(svgString, true);
|
||
|
|
||
|
var afterLoad = options.afterLoad;
|
||
|
if (afterLoad) {
|
||
|
// Invoke afterLoad hook which may modify the SVG element. After load may also return a new
|
||
|
// svg element or svg string
|
||
|
var svgElemOrSvgString = afterLoad(svgElem, svgString) || svgElem;
|
||
|
if (svgElemOrSvgString) {
|
||
|
// Update svgElem and svgString because of modifications to the SVG element or SVG string in
|
||
|
// the afterLoad hook, so the modified SVG is also used for all later cached injections
|
||
|
var isString = typeof svgElemOrSvgString == 'string';
|
||
|
svgString = isString ? svgElemOrSvgString : svgElemToSvgString(svgElem);
|
||
|
svgElem = isString ? buildSvgElement(svgElemOrSvgString, true) : svgElemOrSvgString;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (svgElem instanceof SVGElement) {
|
||
|
var hasUniqueIds = NULL;
|
||
|
if (makeIdsUniqueOption) {
|
||
|
hasUniqueIds = makeIdsUnique(svgElem, false);
|
||
|
}
|
||
|
|
||
|
if (useCacheOption) {
|
||
|
var uniqueIdsSvgString = hasUniqueIds && svgElemToSvgString(svgElem);
|
||
|
// set an array with three entries to the load cache
|
||
|
setSvgLoadCacheValue([hasUniqueIds, svgString, uniqueIdsSvgString]);
|
||
|
}
|
||
|
|
||
|
inject(imgElem, svgElem, absUrl, options);
|
||
|
} else {
|
||
|
svgInvalid(imgElem, options);
|
||
|
setSvgLoadCacheValue(SVG_INVALID);
|
||
|
}
|
||
|
onFinish();
|
||
|
}, function() {
|
||
|
loadFail(imgElem, options);
|
||
|
setSvgLoadCacheValue(LOAD_FAIL);
|
||
|
onFinish();
|
||
|
});
|
||
|
} else {
|
||
|
if (Array.isArray(svgInjectAttributeValue)) {
|
||
|
// svgInjectAttributeValue is an array. Injection is not complete so register callback
|
||
|
svgInjectAttributeValue.push(callback);
|
||
|
} else {
|
||
|
callback();
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
imgNotSet();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Sets the default [options](#options) for SVGInject.
|
||
|
*
|
||
|
* @param {Object} [options] - default [options](#options) for an injection.
|
||
|
*/
|
||
|
SVGInject.setOptions = function(options) {
|
||
|
defaultOptions = mergeOptions(defaultOptions, options);
|
||
|
};
|
||
|
|
||
|
|
||
|
// Create a new instance of SVGInject
|
||
|
SVGInject.create = createSVGInject;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Used in onerror Event of an `<img>` element to handle cases when the loading the original src fails
|
||
|
* (for example if file is not found or if the browser does not support SVG). This triggers a call to the
|
||
|
* options onFail hook if available. The optional second parameter will be set as the new src attribute
|
||
|
* for the img element.
|
||
|
*
|
||
|
* @param {HTMLImageElement} img - an img element
|
||
|
* @param {String} [fallbackSrc] - optional parameter fallback src
|
||
|
*/
|
||
|
SVGInject.err = function(img, fallbackSrc) {
|
||
|
if (img) {
|
||
|
if (img[__SVGINJECT] != FAIL) {
|
||
|
removeEventListeners(img);
|
||
|
|
||
|
if (!IS_SVG_SUPPORTED) {
|
||
|
svgNotSupported(img, defaultOptions);
|
||
|
} else {
|
||
|
removeOnLoadAttribute(img);
|
||
|
loadFail(img, defaultOptions);
|
||
|
}
|
||
|
if (fallbackSrc) {
|
||
|
removeOnLoadAttribute(img);
|
||
|
img.src = fallbackSrc;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
imgNotSet();
|
||
|
}
|
||
|
};
|
||
|
|
||
|
window[globalName] = SVGInject;
|
||
|
|
||
|
return SVGInject;
|
||
|
}
|
||
|
|
||
|
var SVGInjectInstance = createSVGInject('SVGInject');
|
||
|
|
||
|
if (typeof module == 'object' && typeof module.exports == 'object') {
|
||
|
module.exports = SVGInjectInstance;
|
||
|
}
|
||
|
})(window, document);
|