/* This jQuery plugin replaces SVG images in HTML 'img' elements (and optionally CSS in background-images) with PNG replacement images. This is accomplished by the use of a remote image replacement service. Copyright © 2013-2014 - Authors: * Dirk Groenen [Bitlabs Development - dirk@bitlabs.nl] Original author * Craig Fowler [CSF Software Limited - craig@csf-dev.com] Redesign and enhancements Version 3.0.1 --- Copyright 2013-2014 Dirk Groenen Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ /** * Replaces matched SVG images in HTML 'img' elements (and optionally CSS background-image properties) with PNG * replacement images, generated by a remote server/service API. * * ------- * Options * ------- * * temporaryHoldingImage [string] This is a URI to an image which will be used as a "holding" image for the SVG * replacements until the URI of the appropriate PNG replacement has been retrieved from the * remote server. If set to null (the default behaviour) then no holding image will be used. * * * forceReplacements [boolean] If set to true then SVGMagic will replace SVG images even when the web browser * reports that it has native SVG support. If set to false (the default behaviour) then * replacement will only be performed when the web browser does not natively support SVG * images. * * * handleBackgroundImages [boolean] If set to true then SVGMagic will inspect the CSS background-image property of * matched elements. If the background image is an SVG then replacement will additionally * be performed upon the background image. If set to false (the default behaviour) then no * attempt will be made to detect and replace CSS background images. * * Note that even when this option is enabled, background images are only detected on matched * elements. No DOM search is performed to discover background images on (for example) child * nodes. * * * additionalRequestData [object] This is an object representing key/value pairs of information to send to the * remote server as part of the request. The default is an empty object (resulting in no * additional data being sent). * * This option is affected by the deprecated options 'secure' and 'dumpcache'. Presently, * in order to preserve backwards-compatibility, the keys 'secure' and 'dumpcache' will * be added to the request data if they are not already present. The values of these keys * will contain the respective values of those deprecated options. * * Additionally, the key 'svgsources' is reserved and will always be overwritten with an * array of the URIs to the SVG images to be replaced. For this reason, the key 'svgsources' * must not be used within the additional request data. * * * postReplacementCallback [function(replacedImages)] This is a callback function which is executed after all of the * image replacement URIs have been retrieved from the remote server and the replacements * have been performed. * * The parameter passed to this callback is a JavaScript array of objects which have the * following structure: * { * element [object] A reference to the jQuery element node upon which the image * replacement has been performed. * * isBackground [boolean] True if the the replacement was made upon a CSS * background-image. Otherwise this is an HTML 'img' element. * * originalUri [string] The original URI of the SVG image which has been replaced. * * replacementUri [string] The URI of the PNG replacement image. * } * * * remoteServerUri [string] This is the URI to the remote server API endpoint which converts SVG images into * PNGs and returns the list of URIs to those PNG replacements. It is set by default to: * http://svgmagic.bitlabs.nl/converter.php * * If you wish to host your own image-conversion server script/service then replace this with * the URI of your own API endpoint. * * * remoteRequestType [string] This is the type of HTTP request which will be used to communicate with the * remote server/service endpoint. By default this is set to 'POST'. Be careful if hosting * a service which accepts HTTP GET requests, as it could be vulnerable to CSRF attacks. * * * remoteDataType [string] This is the data-type sent to and received from the remote server. By default * this is set to 'json'. * * * replacementUriCreator [function(jQueryElement, originalUri, isBackground)] If provided, this option alters the * behaviour of SVGMagic, short-cutting out the initial call to the remote server, to * retrieve the URIs to the replacement PNG images. This is suitable for use in specialised * scenarios when the creation of the URI for the PNG replacements may be accomplished * entirely in JavaScript. The server hosting those replacement PNG images must also be * capable of serving the correct image with only a single GET URI (as the server will not * have been pre-notified of the path to the SVG source file). As such, it is most likely to * be used in hosted applications on a single domain, in which server-side logic has been * created to serve pre-ordained PNG replacement images. * * The parameters which this function receives are: * * [object] A reference to the jQuery object representing the HTML node on which the * replacement is to be made. * * [string] The URI of the original SVG image to be replaced. * * [boolean] True if the replacement is a CSS background-image, false if it is an HTML * image element. * * The function (if present) must return a string. This string indicates the URI to the * PNG replacement image. If null is returned then the replacement is skipped and the * original SVG image is left in-place. * * The default behaviour (in which this function is null/not-provided) uses a call to a * remote server/API endpoint containing a list of the URIs of the SVG images to be replaced, * The response is parsed for the URIs of the replacement PNG images. * * * debug [boolean] When enabled the script will provide usefull debug information. By default this option is * set to 'false'. Please remember to disable this option when in a production environment. * ------------------ * Deprecated options * ------------------ * * preloader [string or boolean] This is the URI to an image file which is used as a "holding" image * for your SVG images while the PNG replacements load from the remote server. If set to * boolean false (the default behaviour) then no such holding image is used. * * DEPRECATED: Use 'temporaryHoldingImage' instead. If 'temporaryHoldingImage' is set then * this option is ignored. * * * testmode [boolean] If set to true then the SVG replacement will be forced on all browsers, * including those which report that they support SVG natively. If set to false (the default * behaviour) then the SVG replacement will only be performed upon browsers which do not * support it natively. * * DEPRECATED: Use 'forceReplacements' instead. * * * secure [boolean] The value (true or false) of this option is passed to the remote SVG replacement * server as part of the HTTP POST parameters. Whilst the server may honour it or not, it is * intended that if the value is true, then the remote server will return a series of HTTPS * URIs (for the PNG replacement images). If set to false (the default behaviour) then the * remote server should return HTTP (non-secured) URIs. * * Regardless of the setting of this option - the initial call to the remote server will be * performed via unsecured HTTP. * * DEPRECATED: Use 'additionalRequestData' instead, adding data which the server will * interpret in order to serve HTTPS image URIs. The value of this option will be appended * to the additional request data before it is sent to the server. * * * callback [function()] An optional callback function which executes once all of the PNG replacement * image URIs have been retrieved from the remote server and all of the SVG images have had * their URIs replaced. This is not quite a callback which executes after the replacement * images have loaded. If the value is set to false (the default behaviour) then no * additional callback is executed. No parameters are passed to this callback. * * DEPRECATED: Use 'postReplacementCallback' instead. If 'postReplacementCallback' is set * then this option is ignored. * * * backgroundimage [boolean] If set to true then additional inspection will be performed upon all matched * elements in order to find a CSS background-image property. If such a property is found * then it will be included in the replacement process. If set to false (the default * behaviour) then no additional work will be performed to find background-images which are * SVG. * * DEPRECATED: Use 'handleBackgroundImages' instead. * * * dumpcache [boolean] The value (true or false) of this option is passed to the remote SVG replacement * server as part of the HTTP POST parameters. If set to true, then the server is requested * to clear any cached PNG copy of the replaced SVG image. This will result in the remote * server re-generating the PNG replacement. If set to false (the default behaviour) then * the remote server is expected to serve cached PNG replacement images where possible. * * DEPRECATED: Use 'additionalRequestData' instead, adding data which the server will * interpret as a request to drop its cache. The value of this option will be appended * to the additional request data before it is sent to the server. */ (function($) { $.fn.svgmagic = function(givenOptions) { var defaultOptions = { // Deprecated options preloader: false, testmode: false, secure: false, callback: false, backgroundimage: false, dumpcache: false, // Replacements for deprecated options temporaryHoldingImage: null, forceReplacements: false, handleBackgroundImages: false, additionalRequestData: {}, postReplacementCallback:null, // New options remoteServerUri: 'http://bitlabs.nl/svgmagic/converter/3/', remoteRequestType: 'POST', remoteDataType: 'jsonp', debug: false, // TODO: Implement this option replacementUriCreator: null }, untidyOptions = $.extend(defaultOptions, givenOptions), options = tidyOptions(untidyOptions), holdingImageTimeouts = {}, matchedNodes = this, images = [], imgElementName = 'img', srcAttributeName = 'src', backgroundImagePropertyName = 'background-image', urlMatcher = /^url\(["']?([^"'()]+)["']?\)$/, svgExtension = /\.svg$/, svgDataUri = /^data:image\/svg\+xml/, holdingImageTimeoutDuration = 500, VERSION = 3.0; /** * Check if the remoteServerUri has to be replaced with https */ if(window.location.protocol == "https:" || options.additionalRequestData.secure) { options.remoteServerUri = options.remoteServerUri.replace("http://", "https://"); } log(false, "Using " + options.remoteServerUri + " as remote server"); /** * The place where all magic starts */ if(shouldPerformReplacement(options)) { getReplacementUris(options, matchedNodes); } /** * Include JSON if it's not available in the browser */ if (typeof JSON == 'undefined') { if(typeof JSON!=="object"){JSON={}}(function(){"use strict";function f(e){return e<10?"0"+e:e}function quote(e){escapable.lastIndex=0;return escapable.test(e)?'"'+e.replace(escapable,function(e){var t=meta[e];return typeof t==="string"?t:"\\u"+("0000"+e.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+e+'"'}function str(e,t){var n,r,i,s,o=gap,u,a=t[e];if(a&&typeof a==="object"&&typeof a.toJSON==="function"){a=a.toJSON(e)}if(typeof rep==="function"){a=rep.call(t,e,a)}switch(typeof a){case"string":return quote(a);case"number":return isFinite(a)?String(a):"null";case"boolean":case"null":return String(a);case"object":if(!a){return"null"}gap+=indent;u=[];if(Object.prototype.toString.apply(a)==="[object Array]"){s=a.length;for(n=0;n 0) { if(replacementFunction && typeof replacementFunction == 'function') { for(var i = 0; i < images.length; i++) { var image = images[i]; image.replacementUri = replacementFunction(image.element, image.originalUri, image.isBackground); } performReplacements(opts); } else { getReplacementUrisFromRemoteService(opts); } } } /** * Gets all of the replacement image URIs from a remote server using an API call. */ function getReplacementUrisFromRemoteService(opts) { var sources = [], data = {}; for(var i = 0; i < images.length; i++) { sources.push(images[i].originalUri); } // Get baseurl var baseUrl = window.location.href.split('/'); $.extend(data, opts.additionalRequestData, { svgsources: sources, version: VERSION, origin: baseUrl[2] }); $.ajax({ dataType: opts.remoteDataType, method: opts.remoteRequestType, url: opts.remoteServerUri, data: data, timeout: 3000, success: function(response, textStatus, jqXHR) { for(var i = 0; i < images.length; i++) { var image = images[i], responseUri = response.images[i].image; image.replacementUri = responseUri; image.error = response.images[i].error; image.responseMsg = response.images[i].msg; image.filename = response.images[i].filename; } performReplacements(opts); }, error: function(){ log(true, "The request took longer than 3 seconds to complete. No image were replaced. Use the developer tools to check wheter the server responded with an error.") } }); } /** * Performs image replacements using the result from the remote replacement service. */ function performReplacements(opts) { for(var i = 0; i < images.length; i++) { var image = images[i], newUri = image.replacementUri; if(!newUri) { log(true, image.filename + ": No new url received"); continue; } else if(image.error) { log(true, image.filename + ": " + image.responseMsg); continue; } else if(!image.isBackground) { log(false, image.filename + ": Image replaced. Server responded with: " + image.responseMsg); if(opts.temporaryHoldingImage) { clearTimeout(holdingImageTimeouts[i]); } image.element.attr(srcAttributeName, newUri); } else { log(false, image.filename + ": Background image replaced. Server responded with: " + image.responseMsg); image.element.css(backgroundImagePropertyName, 'url("' + newUri + '")'); } } if(opts.postReplacementCallback && typeof opts.postReplacementCallback == 'function') { opts.postReplacementCallback(images); } } /** * Tidies up an object containing options for this plugin. Takes any deprecated options (where set) and writes * them into the equivalent replacement option. */ function tidyOptions(originalOptions) { if(!originalOptions.temporaryHoldingImage && originalOptions.preloader && typeof originalOptions.preloader == 'string') { originalOptions.temporaryHoldingImage = originalOptions.preloader; } if(originalOptions.testmode && typeof originalOptions.testmode == 'boolean') { originalOptions.forceReplacements = true; } if(!originalOptions.postReplacementCallback && originalOptions.callback && typeof originalOptions.callback == 'function') { originalOptions.postReplacementCallback = function(replacedImages) { originalOptions.callback(); }; } if(!originalOptions.additionalRequestData['secure']) { originalOptions.additionalRequestData.secure = originalOptions.secure; } if(!originalOptions.additionalRequestData['dumpcache']) { originalOptions.additionalRequestData.dumpcache = originalOptions.dumpcache; } return originalOptions; } /* * Create console log message of option has been enabled */ function log(error, msg){ if(options.debug){ if(error) console.error(msg) else console.log(msg); } } // Return the original jQuery object, standard jQuery behaviour. return this; }; }(jQuery));