|
@@ -0,0 +1,697 @@
|
|
|
+/**
|
|
|
+ * 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);
|