blob: a73098247dad38cd105e5b5da3c732f86f4868b4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2014-2018 BSI Business Systems Integration AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* BSI Business Systems Integration AG - initial API and implementation
******************************************************************************/
/**
* jQuery plugin with scout extensions
*/
/**
* JS Type definition for jQuery Promise (which is actually a Deferred, but does not exist as type).
* @typedef {object} Promise
*/
// === internal methods ===
/**
* Returns false when the component display is 'none', otherwise true.
*
* Note: this gives other results than $.is(':visible'), since that method will also return false
* when a component has absolute positioning and no width and height is defined (well, you cannot
* see a component with a style like this, but technically it is not set to 'not visible').
*
* Also note that this function _only_ checks the 'display' property! Other methods to make an element
* invisible to the user ('visibility: hidden', 'opacity: 0', off-screen position etc.) are _not_
* considered.
*/
function elemVisible(elem) {
// Check if element itself is hidden by its own style attribute
if (!elem || isHidden(elem.style)) {
return false;
}
// Must use correct window for element / computedStyle
var myWindow = (elem instanceof Document ? elem : elem.ownerDocument).defaultView;
// Check if element itself is hidden by external style-sheet
if (isHidden(myWindow.getComputedStyle(elem))) {
return false;
}
// Else visible
return true;
// ----- Helper functions -----
function isHidden(style) {
return style.display === 'none';
}
}
function explodeShorthandProperties(properties) {
var newProperties = [];
properties.forEach(function(prop) {
// shorthand css properties may not be copied directly (at least not in firefox) -> copy the actual properties
if (prop === 'margin' || prop === 'padding') {
newProperties.push(prop + '-top');
newProperties.push(prop + '-right');
newProperties.push(prop + '-bottom');
newProperties.push(prop + '-left');
} else if (prop === 'border') {
newProperties.push('border-top-style');
newProperties.push('border-right-style');
newProperties.push('border-bottom-style');
newProperties.push('border-left-style');
newProperties.push('border-top-color');
newProperties.push('border-right-color');
newProperties.push('border-bottom-color');
newProperties.push('border-left-color');
newProperties.push('border-top-width');
newProperties.push('border-right-width');
newProperties.push('border-bottom-width');
newProperties.push('border-left-width');
} else {
newProperties.push(prop);
}
});
return newProperties;
}
// === $ extensions ===
/*!
* jQuery UI Widget 1.11.2
* http://jqueryui.com
*
* Copyright 2014 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/jQuery.widget/
*/
/**
* This function is copied from jQuery UI. It is used to fire a 'remove' event
* when we call the .remove() function on a jQuery object.
*/
$.cleanData = (function(orig) {
return function(elems) {
var events, elem, i;
for (i = 0;
(elem = elems[i]); i++) { // NOSONAR
try {
// Only trigger remove when necessary to save time
events = $._data(elem, 'events');
if (events && events.remove) {
$(elem).triggerHandler('remove');
}
// http://bugs.jquery.com/ticket/8235
} catch (e) {
// NOP
}
}
orig(elems);
};
})($.cleanData);
/**
* Used by some animate functions.
*/
$.removeThis = function() {
$(this).remove();
};
/**
* Convenience function that can be used as an jQuery event handler, when this
* event should be "swallowed". Technically, this function calls preventDefault(),
* stopPropagation() and stopImmediatePropagation() on the event.
*
* Note: "return false" is equal to preventDefault() and stopPropagation(), but
* not stopImmediatePropagation().
*/
$.suppressEvent = function(event) {
if (event) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
}
};
/**
* If the event target is disabled (according to $.fn.isEnabled()), the event is suppressed
* and the method returns true. Otherwise, false is returned.
*/
$.suppressEventIfDisabled = function(event, $target) {
$target = $target || $(event.target);
if (!$target.isEnabled()) {
$.suppressEvent(event);
return true;
}
return false;
};
/**
* Implements the 'debounce' pattern. The given function fx is executed after a certain delay
* (in milliseconds), but if the same function is called a second time within the waiting time,
* depending on the 'reschedule' option, the timer is reset or the second call is ignored.
* The default value for the 'delay' option is 250 ms.
*
* The resulting function has a function member 'cancel' that can be used to clear any scheduled
* calls to the original function. If no such call was scheduled, cancel() returns false,
* otherwise true.
*
* OPTION DEFAULT VALUE DESCRIPTION
* ------------------------------------------------------------------------------------
* delay 250 Waiting time in milliseconds before the function is executed.
*
* reschedule true Defines whether subsequent call to the debounced function
* within the waiting time cause the timer to be reset or not.
* If the reschedule option is 'false', subsequent calls within
* the waiting time will just be ignored.
*
* @param fx
* the function to wrap
* @param options
* an optional options object (see table above). Short-hand version: If a number is passed instead
* of an object, the value is automatically converted to the option 'delay'.
*/
$.debounce = function(fx, options) {
if (typeof options === 'number') {
options = {
delay: options
};
}
options = $.extend({
delay: 250,
reschedule: true
}, options);
var timeoutId = null;
var fn = function() {
var that = this,
args = arguments;
if (timeoutId && !options.reschedule) {
// Function is already schedule but 'reschedule' option is set to false --> discard this request
return;
}
if (timeoutId) {
// Function is already scheduled --> cancel current scheduled call and re-schedule the call
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
timeoutId = null;
fx.apply(that, args);
}, options.delay);
};
fn.cancel = function() {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
return true;
}
return false;
};
return fn;
};
/**
* Executes the given function. Further calls to the same function are delayed by the given delay
* (default 250ms). This is similar to $.debounce() but ensures that function is called at least
* every 'delay' milliseconds. Can be useful to prevent too many function calls, e.g. from UI events.
*/
$.throttle = function(fx, delay) {
delay = (delay !== undefined) ? delay : 250;
var timeoutId = null;
var lastExecution;
return function() {
var that = this,
args = arguments,
now = new Date().getTime(),
callFx = function() {
lastExecution = now;
fx.apply(that, args);
};
if (lastExecution && lastExecution + delay > now) {
clearTimeout(timeoutId);
timeoutId = setTimeout(function() {
callFx();
}, delay);
} else {
callFx();
}
};
};
/**
* Returns a function which negates the return value of the given function when called.
*/
$.negate = function(fx) {
return function() {
return !fx.apply(this, arguments);
};
};
/**
* color calculation
*/
$.colorOpacity = function(hex, opacity) {
// validate hex string
hex = String(hex).replace(/[^0-9a-f]/gi, '');
if (hex.length < 6) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
opacity = opacity || 0;
// convert to decimal and change luminosity
var rgb = '#';
for (var i = 0; i < 3; i++) {
var c = parseInt(hex.substr(i * 2, 2), 16);
c = Math.round(Math.min(Math.max(0, 255 - (255 - c) * opacity), 255)).toString(16);
rgb += ('00' + c).substr(c.length);
}
return rgb;
};
/**
* CSP-safe method to dynamically load and execute a script from server.
*
* A new <script> tag is added to the document's head element. The methods returns
* a promise which can be used to execute code after the loading has been completed.
* A jQuery object referring to the new script tag is passed to the promise's
* callback functions.
*
* $.injectScript('http://server/path/script.js')
* .done(function($scriptTag) { ... });
*
* Options (optional):
*
* NAME DEFAULT DESCRIPTION
* --------------------------------------------------------------------------------------------
* document window.document Which document to inject the script to.
*
* removeTag false Whether to remove the script tag again from the DOM
* after the script has been loaded.
*/
$.injectScript = function(url, options) {
options = options || {};
var deferred = $.Deferred();
var myDocument = options.document || window.document;
var linkTag = myDocument.createElement('script');
$(linkTag)
.attr('src', url)
.attr('async', true)
.on('load error', function(event) {
if (options.removeTag) {
myDocument.head.removeChild(linkTag);
}
if (event.type === 'error') {
deferred.reject($(linkTag));
} else {
deferred.resolve($(linkTag));
}
});
// Use raw JS function to append the <script> tag, because jQuery handles
// script tags specially (see "domManip" function) and uses eval() which
// is not CSP-safe.
myDocument.head.appendChild(linkTag);
return deferred.promise();
};
/**
* CSP-safe method to dynamically load a style sheet from server.
*
* A new <link> tag is added to the document's head element. The methods returns
* a promise which can be used to execute code after the loading has been completed.
* A jQuery object referring to the new link tag is passed to the promise's
* callback functions.
*
* $.injectStyleSheet('http://server/path/style.css')
* .done(function($linkTag) { ... });
*
* Options (optional):
*
* NAME DEFAULT DESCRIPTION
* --------------------------------------------------------------------------------------------
* document window.document Which document to inject the style sheet to.
*/
$.injectStyleSheet = function(url, options) {
options = options || {};
var deferred = $.Deferred();
var myDocument = options.document || window.document;
var linkTag = myDocument.createElement('link');
$(linkTag)
.attr('rel', 'stylesheet')
.attr('type', 'text/css')
.attr('href', url)
.on('load error', function(event) {
if (event.type === 'error') {
deferred.reject($(linkTag));
} else {
deferred.resolve($(linkTag));
}
});
// Use raw JS function to append the <script> tag, because jQuery handles
// script tags specially (see "domManip" function) and uses eval() which
// is not CSP-safe.
myDocument.head.appendChild(linkTag);
return deferred.promise();
};
/**
* Dynamically adds styles to the document.
*
* A new <style> tag is added to the document's head element. The methods returns
* a jQuery object referring to the new style tag.
*
* $styleTag = $.injectStyle('p { text-color: orange; }');
*
* Options (optional):
*
* NAME DEFAULT DESCRIPTION
* --------------------------------------------------------------------------------------------
* document window.document Which document to inject the style to.
*/
$.injectStyle = function(data, options) {
options = options || {};
var myDocument = options.document || window.document;
var styleTag = myDocument.createElement('style');
var $styleTag = $(styleTag);
$styleTag
.attr('type', 'text/css')
.html(data);
myDocument.head.appendChild(styleTag);
return $styleTag;
};
$.pxToNumber = function(pixel) {
if (!pixel) {
// parseFloat would return NaN if pixel is '' or undefined
return 0;
}
// parseFloat ignores 'px' and just extracts the number
return parseFloat(pixel, 10);
};
/**
* Use this function as shorthand of this:
* <code>$.Deferred().resolve([arguments]);</code>
*
* @param {object[]} [arguments] of this function are passed to the resolve function of the deferred
* @returns {$.Deferred} a deferred for an already resolved jQuery.Deferred object.
*/
$.resolvedDeferred = function() {
var deferred = $.Deferred();
deferred.resolve.apply(deferred, arguments);
return deferred;
};
/**
* Use this function as shorthand of this:
* <code>$.Deferred().resolve([arguments]).promise();</code>
*
* @param {object[]} [arguments] of this function are passed to the resolve function of the deferred
* @returns {Promise} a promise for an already resolved jQuery.Deferred object.
*/
$.resolvedPromise = function() {
var deferred = $.Deferred();
deferred.resolve.apply(deferred, arguments);
return deferred.promise();
};
/**
* Use this function as shorthand of this:
* <code>$.Deferred().reject([arguments]).promise();</code>
*
* @param {object[]} [arguments] of this function are passed to the reject function of the deferred
* @returns {Promise} a promise for an already rejected jQuery.Deferred object.
*/
$.rejectedPromise = function() {
var deferred = $.Deferred();
deferred.reject.apply(deferred, arguments);
return deferred.promise();
};
/**
* Creates a new promise which resolves when all promises resolve and fails when the first promise fails.
*
* @param asArray (optional) when set to true, the resolve function will transform the
* flat arguments list containing the results into an array. The arguments of the reject function won't be touched. Default is false.
*/
$.promiseAll = function(promises, asArray) {
asArray = scout.nvl(asArray, false);
promises = scout.arrays.ensure(promises);
var deferred = $.Deferred();
$.when.apply($, promises).done(function() {
if (asArray) {
deferred.resolve(scout.objects.argumentsToArray(arguments));
} else {
deferred.resolve.apply(this, arguments);
}
}).fail(function() {
deferred.reject.apply(this, arguments);
});
return deferred.promise();
};
/**
* Shorthand for an AJAX request for a JSON file with UTF8 encoding.
* Errors are caught and converted to a rejected promise with the following
* arguments: jqXHR, textStatus, errorThrown, requestOptions.
*
* @returns a promise from JQuery function $.ajax
*/
$.ajaxJson = function(url) {
return $.ajax({
url: url,
dataType: 'json',
contentType: 'application/json; charset=UTF-8'
}).catch(function() {
// Reject the promise with usual arguments (jqXHR, textStatus, errorThrown), but add the request
// options as additional argument (e.g. to make the URL available to the error handler)
var args = scout.objects.argumentsToArray(arguments);
args.push(this);
return $.rejectedPromise.apply($, args);
});
};
/**
* Ensures the given parameter is a jQuery object.<p>
* If it is a jQuery object, it will be returned as it is.
* If it isn't, it will be passed to $() in order to create one.
* <p>
* Just using $() on an existing jQuery object would clone it which would work but is unnecessary.
*/
$.ensure = function($elem) {
if ($elem instanceof $) {
return $elem;
}
return $($elem);
};
/**
* Helper function to determine if an object is of type "jqXHR" (http://api.jquery.com/jQuery.ajax/#jqXHR)
*/
$.isJqXHR = function(obj) {
return (typeof obj === 'object' && obj.hasOwnProperty('readyState') && obj.hasOwnProperty('status') && obj.hasOwnProperty('statusText'));
};
// === $.easing extensions ===
$.extend($.easing, {
easeOutQuart: function(x) {
return 1 - Math.pow(1 - x, 4);
}
});
// === $.prototype extensions ===
$.fn.nvl = function($element) {
if (this.length || !($element instanceof $)) {
return this;
}
return $element;
};
/**
* @param element string. Example = &lt;input&gt;
* @param cssClass (optional) class attribute
* @param text (optional) adds a child text-node with given text (no HTML content)
*/
$.fn.makeElement = function(element, cssClass, text) {
var myDocument = this.document(true);
if (myDocument === undefined || element === undefined) {
return new Error('missing arguments: document, element');
}
var $element = $(element, myDocument);
if (cssClass) {
$element.addClass(cssClass);
}
if (text) {
$element.text(text);
}
return $element;
};
/**
* Creates a DIV element in the current document. The function adds an unselectable attribute,
* if this is required by the current device (@see Device.js). When you don't want the (un-)
* selectable behavior use <code>makeElement('&lt;div&gt;')</code>.
*
* @param cssClass (optional) string added to the 'class' attribute
* @param text (optional) string used as inner text
*/
$.fn.makeDiv = function(cssClass, text) {
var $div = this.makeElement('<div>', cssClass, text);
// scout.device may not be initialized yet (e.g. before app is created or if app bootstrap fails)
var unselectable = (scout.device ? scout.device.unselectableAttribute : scout.Device.DEFAULT_UNSELECTABLE_ATTRIBUTE);
if (unselectable.key) {
$div.attr(unselectable.key, unselectable.value);
}
return $div;
};
$.fn.makeSpan = function(cssClass, text) {
return this.makeElement('<span>', cssClass, text);
};
/**
* @returns HTML document reference (ownerDocument) of the HTML element.
* @param domElement (optional) if true the result is returned as DOM element, otherwise it is returned as jQuery object. The default is false.
*/
$.fn.document = function(domElement) {
var myDocument = this.length ? (this[0] instanceof Document ? this[0] : this[0].ownerDocument) : null;
return domElement ? myDocument : $(myDocument);
};
/**
* @returns HTML window reference (defaultView) of the HTML element
* @param domElement (optional) if true the result is returned as DOM element, otherwise it is returned as jQuery object. The default is false.
*/
$.fn.window = function(domElement) {
var myDocument = this.document(true),
myWindow = myDocument ? myDocument.defaultView : null;
return domElement ? myWindow : $(myWindow);
};
/**
* @returns the active element of the current document
* @param domElement (optional) if true the result is returned as DOM element, otherwise it is returned as jQuery object. The default is false.
*/
$.fn.activeElement = function(domElement) {
var myDocument = this.document(true),
activeElement = myDocument ? myDocument.activeElement : null;
return domElement ? activeElement : $(activeElement);
};
/**
* @returns the BODY element of the HTML document in which the current HTML element is placed.
* @param domElement (optional) if true the result is returned as DOM element, otherwise it is returned as jQuery object. The default is false.
*/
$.fn.body = function(domElement) {
var $body = $('body', this.document(true));
return domElement ? $body[0] : $body;
};
/**
* @returns the closest DOM element that has the 'scout' class.
* @param domElement (optional) if true the result is returned as DOM element, otherwise it is returned as jQuery object. The default is false.
*/
$.fn.entryPoint = function(domElement) {
var $element = this.closest('.scout');
return domElement ? $element[0] : $element;
};
/**
* @returns {scout.Dimension} size of the window (width and height)
*/
$.fn.windowSize = function() {
var $window = this.window();
return new scout.Dimension($window.width(), $window.height());
};
/**
* Returns the element at the given point considering only child elements and elements matching the selector, if specified.
*/
$.fn.elementFromPoint = function(x, y, selector) {
var $container = $(this),
doc = $container.document(true),
elements = [],
i = 0,
$element;
if (!doc) {
// If doc is null the $container itself is the document
doc = $container[0];
}
if (!doc) {
// If doc is still null (e.g. because the current jQuery collection does not contain any elements) return an empty collection
return $();
}
while (true) {
$element = $(doc.elementFromPoint(x, y));
if ($element.length === 0 || $element[0] === doc.documentElement) {
break;
}
if ($container.isOrHas($element) && (!selector || $element.is(selector))) {
break;
}
elements.push($element);
// make the element invisible to get the underlying element (uses visibility: hidden to make sure element size and position won't be changed)
$element.addClass('invisible');
i++;
if (i > 1000) {
$.log.warn('Infinite loop aborted', $element);
$element = $();
break;
}
}
if ($element[0] === doc.documentElement && $container[0] !== doc) {
// return an empty element if the only element found is the document element and the document element is not the container
$element = $();
}
elements.forEach(function($element) {
// show element again
$element.removeClass('invisible');
});
return $element;
};
// prepend - and return new div for chaining
$.fn.prependDiv = function(cssClass, text) {
return this.makeDiv(cssClass, text).prependTo(this);
};
// append - and return new div for chaining
$.fn.appendDiv = function(cssClass, text) {
return this.makeDiv(cssClass, text).appendTo(this);
};
$.fn.prependElement = function(element, cssClass, text) {
return this.makeElement(element, cssClass, text).prependTo(this);
};
$.fn.appendElement = function(element, cssClass, text) {
return this.makeElement(element, cssClass, text).appendTo(this);
};
// insert after - and return new div for chaining
$.fn.afterDiv = function(cssClass, text) {
return this.makeDiv(cssClass, text).insertAfter(this);
};
// insert before - and return new div for chaining
$.fn.beforeDiv = function(cssClass, text) {
return this.makeDiv(cssClass, text).insertBefore(this);
};
$.fn.appendSpan = function(cssClass, text) {
return this.makeSpan(cssClass, text).appendTo(this);
};
$.fn.appendBr = function(cssClass) {
return this.makeElement('<br>', cssClass).appendTo(this);
};
$.fn.appendTextNode = function(text) {
return $(this.document(true).createTextNode(text)).appendTo(this);
};
/**
* @param {scout.IconDesc|string} iconId
*/
$.fn.appendIcon = function(iconId, cssClass) {
if (!iconId) {
return this.appendImg(null, cssClass);
}
var icon;
if (iconId instanceof scout.IconDesc) {
icon = iconId;
} else {
icon = scout.icons.parseIconId(iconId);
}
if (icon.isFontIcon()) {
return this.makeSpan(cssClass, icon.iconCharacter)
.addClass('icon')
.addClass(icon.appendCssClass('font-icon'))
.appendTo(this);
}
return this.appendImg(icon.iconUrl, cssClass)
.addClass('icon image-icon');
};
$.fn.appendImg = function(imageSrc, cssClass) {
var $icon = this.appendElement('<img>', cssClass);
if (imageSrc) {
$icon.attr('src', imageSrc);
}
return $icon;
};
$.fn.makeSVG = function(type, cssClass, text, id) {
var myDocument = this.document(true);
if (myDocument === undefined || type === undefined) {
return new Error('missing arguments: document, type');
}
var $svg = $(myDocument.createElementNS('http://www.w3.org/2000/svg', type));
if (cssClass) {
$svg.attr('class', cssClass);
}
if (text) {
$svg.text(text);
}
if (id !== undefined) {
$svg.attr('id', id);
}
return $svg;
};
// append SVG
$.fn.appendSVG = function(type, cssClass, text, id) {
return this.makeSVG(type, cssClass, text, id).appendTo(this);
};
$.fn.attrXLINK = function(attributeName, value) {
if (this.length === 0) { // shortcut for empty collections
return this;
}
if (this.length === 1) { // shortcut for single element collections
this[0].setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:' + attributeName, value);
return this;
}
return this.each(function() {
this.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:' + attributeName, value);
});
};
/**
* This function adds a device specific CSS class to the current element.
* The current implementation adds a class 'ie' if Internet Explorer is used.
*/
$.fn.addDeviceClass = function() {
if (scout.device.isInternetExplorer()) {
this.addClass('ie');
if (scout.device.browserVersion === 9) {
this.addClass('ie9');
}
}
if (scout.device.isIos()) {
this.addClass('ios');
}
return this;
};
// select one and deselect siblings
$.fn.selectOne = function() {
this.siblings().removeClass('selected');
this.addClass('selected');
return this;
};
$.fn.select = function(selected) {
return this.toggleClass('selected', !!selected);
};
$.fn.isSelected = function() {
return this.hasClass('selected');
};
$.fn.setEnabled = function(enabled) {
enabled = !!enabled;
this.toggleClass('disabled', !enabled);
// Toggle disabled attribute for elements that support it (see http://www.w3.org/TR/html5/disabled-elements.html)
if (this.is('button, input, select, textarea, optgroup, option, fieldset')) {
this.toggleAttr('disabled', !enabled);
}
return this;
};
$.fn.isEnabled = function() {
return !this.hasClass('disabled');
};
$.fn.setVisible = function(visible) {
var isVisible = !this.hasClass('hidden');
if (isVisible === visible) {
return this;
}
if (!visible) {
this.addClass('hidden');
this.trigger('hide');
} else {
this.removeClass('hidden');
this.trigger('show');
}
return this;
};
$.fn.isDisplayNone = function() {
return this.css('display') === 'none';
};
$.fn.setTabbable = function(tabbable) {
return this.attr('tabIndex', tabbable ? 0 : null);
};
$.fn.isTabbable = function() {
return this.attr('tabIndex') >= 0;
};
/**
* @param {string} iconId
* @param {function} [addToDomFunc] optional function which is used to add the new icon element to the DOM
* When not set, this.prepend($icon) is called.
* @returns {$}
* @see scout.Icon as an alternative
*/
$.fn.icon = function(iconId, addToDomFunc) {
var icon, $icon = this.data('$icon');
if (iconId) {
icon = scout.icons.parseIconId(iconId);
if (icon.isFontIcon()) {
getOrCreateIconElement.call(this, $icon, '<span>', addToDomFunc)
.addClass('icon')
.addClass(icon.appendCssClass('font-icon'))
.text(icon.iconCharacter);
} else {
getOrCreateIconElement.call(this, $icon, '<img>', addToDomFunc)
.attr('src', icon.iconUrl)
.addClass('icon image-icon');
}
} else {
removeIconElement.call(this, $icon);
}
return this;
// ----- Helper functions -----
function getOrCreateIconElement($icon, newElement, addToDomFunc) {
// If element type does not match existing element, remove the existing element (e.g. when changing from font-icon to picture icon)
if ($icon && !$icon.is(newElement.replace(/[<>]/g, ''))) {
removeIconElement.call(this, $icon);
$icon = null;
}
// Create new element if necessary
if (!$icon) {
$icon = $(newElement);
this.data('$icon', $icon);
if (!addToDomFunc) {
this.prepend($icon);
} else {
addToDomFunc.call(this, $icon);
}
}
return $icon;
}
function removeIconElement($icon) {
if ($icon) {
$icon.remove();
}
this.removeData('$icon');
}
};
$.fn.placeholder = function(placeholder) {
return this.toggleAttr('placeholder', !!placeholder, placeholder);
};
$.fn.isVisible = function() {
return !this.hasClass('hidden');
};
$.fn.isEveryParentVisible = function() {
var everyParentVisible = true;
this.parents().each(function() {
if (!$(this).isVisible()) {
everyParentVisible = false;
return false;
}
});
return everyParentVisible;
};
/**
* @return true if the element is attached (= is in the dom tree), false if not
*/
$.fn.isAttached = function() {
return $.contains(this.document(true).documentElement, this[0]);
};
/**
* @returns the current element if it is scrollable, otherwise the first parent which is scrollable
*/
$.fn.scrollParent = function() {
var $elem = this;
while ($elem.length > 0) {
if ($elem.data('scrollable')) {
return $elem;
}
$elem = $elem.parent();
}
return $();
};
/**
* Returns every parent which is scrollable
*/
$.fn.scrollParents = function() {
var $scrollParents = $(),
$elem = this;
while ($elem.length > 0) {
if ($elem.data('scrollable')) {
$scrollParents.push($elem);
}
$elem = $elem.parent();
}
return $scrollParents;
};
// most used animate
$.fn.animateAVCSD = function(attr, value, complete, step, duration) {
var properties = {};
var options = {};
properties[attr] = value;
if (complete) {
options.complete = complete;
}
if (step) {
options.step = step;
}
if (duration) {
options.duration = duration;
}
options.queue = false;
this.animate(properties, options);
return this;
};
// SVG animate, array contains attr, endValue + startValue
$.fn.animateSVG = function(attr, endValue, duration, complete, withoutTabIndex) {
return this.each(function() {
var startValue = parseFloat($(this).attr(attr));
if (withoutTabIndex) {
var oldComplete = complete;
complete = function() {
if (oldComplete) {
oldComplete.call(this);
}
$(this).removeAttr('tabindex');
};
}
$(this).animate({
tabIndex: 0
}, {
step: function(now, fx) {
this.setAttribute(attr, startValue + (endValue - startValue) * fx.pos);
},
duration: duration,
complete: complete,
queue: false
});
});
};
$.fn.addClassForAnimation = function(className, options) {
var defaultOptions = {
classesToRemove: className
};
options = $.extend({}, defaultOptions, options);
this.addClass(className);
this.oneAnimationEnd(function() {
// remove class, otherwise animation will be executed each time the element changes it's visibility (attach/rerender),
// and even each time when the css classes change
this.removeClass(options.classesToRemove);
}.bind(this));
return this;
};
$.fn.oneAnimationEnd = function(selector, data, handler) {
return this.one('animationend webkitAnimationEnd', selector, data, handler);
};
/**
* Animates from old to new width
*/
$.fn.cssWidthAnimated = function(oldWidth, newWidth, opts) {
opts = opts || {};
opts.duration = scout.nvl(opts.duration, 300);
// Reset to old width first
this.cssWidth(oldWidth);
// Then animate to new width
this.animate({
width: newWidth
}, opts);
return this;
};
$.fn.cssHeightAnimated = function(oldHeight, newHeight, opts) {
opts = opts || {};
opts.duration = scout.nvl(opts.duration, 300);
// Reset to old height first
this.cssHeight(oldHeight);
// Then animate to new height
this.animate({
height: newHeight
}, opts);
return this;
};
$.fn.cssLeftAnimated = function(from, to, opts) {
opts = opts || {};
opts.duration = scout.nvl(opts.duration, 300);
// Reset to from first
this.cssLeft(from);
// Then animate to new width
this.animate({
left: to
}, opts);
return this;
};
$.fn.cssTopAnimated = function(from, to, opts) {
opts = opts || {};
opts.duration = scout.nvl(opts.duration, 300);
// Reset to from first
this.cssTop(from);
// Then animate to new pos
this.animate({
top: to
}, opts);
return this;
};
$.fn.cssAnimated = function(fromVals, toVals, opts) {
opts = opts || {};
opts.duration = scout.nvl(opts.duration, 300);
// Reset to from first
this.css(fromVals);
// Then animate to new pos
this.animate(toVals, opts);
return this;
};
// over engineered animate
$.fn.widthToContent = function(opts) {
var oldW = this.outerWidth(),
newW = this.css('width', 'auto').outerWidth();
this.cssWidthAnimated(oldW, newW, opts);
return this;
};
/**
* Offset to a specific ancestor and not to the document as offset() would do.
* Not the same as position() which returns the position relative to the offset parent.
*/
$.fn.offsetTo = function($to) {
var toOffset = $to.offset(),
offset = this.offset();
return {
top: offset.top - toOffset.top,
left: offset.left - toOffset.left
};
};
$.fn.cssPxValue = function(prop, value) {
if (value === undefined) {
return $.pxToNumber(this.css(prop));
}
if (value === null) {
value = ''; // "null" should also remove the CSS property
}
if (typeof value === 'string') {
return this.css(prop, value);
}
return this.css(prop, value + 'px');
};
$.fn.cssLeft = function(position) {
return this.cssPxValue('left', position);
};
$.fn.cssTop = function(position) {
return this.cssPxValue('top', position);
};
/**
* Sets the CSS properties 'left' and 'top' based on the x and y properties of the given point instance.
*
* @param {scout.Point} point
*/
$.fn.cssPosition = function(point) {
return this.cssLeft(point.x).cssTop(point.y);
};
$.fn.cssBottom = function(position) {
return this.cssPxValue('bottom', position);
};
$.fn.cssRight = function(position) {
return this.cssPxValue('right', position);
};
$.fn.cssWidth = function(width) {
return this.cssPxValue('width', width);
};
$.fn.cssMinWidth = function(minWidth) {
if (minWidth === undefined) {
var value = this.css('min-width');
if (value === 'auto' || value.indexOf('%') !== -1) {
return 0;
}
return $.pxToNumber(value);
}
return this.cssPxValue('min-width', minWidth);
};
/**
* @returns the max-width as number. If max-width is not set (resp. defaults to 'none') Number.MAX_VALUE is returned.
*/
$.fn.cssMaxWidth = function(maxWidth) {
if (maxWidth === undefined) {
var value = this.css('max-width');
if (value === 'none' || value.indexOf('%') !== -1) {
return Number.MAX_VALUE;
}
return $.pxToNumber(value);
}
return this.cssPxValue('max-width', maxWidth);
};
$.fn.cssHeight = function(height) {
return this.cssPxValue('height', height);
};
$.fn.cssMinHeight = function(minHeight) {
if (minHeight === undefined) {
var value = this.css('min-height');
if (value === 'auto' || value.indexOf('%') !== -1) {
return 0;
}
return $.pxToNumber(value);
}
return this.cssPxValue('min-height', minHeight);
};
/**
* @returns the max-height as number. If max-height is not set (resp. defaults to 'none') Number.MAX_VALUE is returned.
*/
$.fn.cssMaxHeight = function(maxHeight) {
if (maxHeight === undefined) {
var value = this.css('max-height');
if (value === 'none' || value.indexOf('%') !== -1) {
return Number.MAX_VALUE;
}
return $.pxToNumber(value);
}
return this.cssPxValue('max-height', maxHeight);
};
$.fn.cssLineHeight = function(height) {
return this.cssPxValue('line-height', height);
};
$.fn.cssMarginLeft = function(value) {
return this.cssPxValue('margin-left', value);
};
$.fn.cssMarginBottom = function(value) {
return this.cssPxValue('margin-bottom', value);
};
$.fn.cssMarginRight = function(value) {
return this.cssPxValue('margin-right', value);
};
$.fn.cssMarginTop = function(value) {
return this.cssPxValue('margin-top', value);
};
$.fn.cssMarginX = function(value) {
if (value === undefined) {
return this.cssMarginLeft() + this.cssMarginRight();
}
this.cssMarginLeft(value);
this.cssMarginRight(value);
return this;
};
$.fn.cssMarginY = function(value) {
if (value === undefined) {
return this.cssMarginTop() + this.cssMarginBottom();
}
this.cssMarginTop(value);
this.cssMarginBottom(value);
return this;
};
$.fn.cssPaddingTop = function(value) {
return this.cssPxValue('padding-top', value);
};
$.fn.cssPaddingRight = function(value) {
return this.cssPxValue('padding-right', value);
};
$.fn.cssPaddingBottom = function(value) {
return this.cssPxValue('padding-bottom', value);
};
$.fn.cssPaddingLeft = function(value) {
return this.cssPxValue('padding-left', value);
};
$.fn.cssPaddingX = function(value) {
if (value === undefined) {
return this.cssPaddingLeft() + this.cssPaddingRight();
}
this.cssPaddingLeft(value);
this.cssPaddingRight(value);
return this;
};
$.fn.cssPaddingY = function(value) {
if (value === undefined) {
return this.cssPaddingTop() + this.cssPaddingBottom();
}
this.cssPaddingTop(value);
this.cssPaddingBottom(value);
return this;
};
$.fn.cssBorderBottomWidth = function(value) {
return this.cssPxValue('border-bottom-width', value);
};
$.fn.cssBorderLeftWidth = function(value) {
return this.cssPxValue('border-left-width', value);
};
$.fn.cssBorderRightWidth = function(value) {
return this.cssPxValue('border-right-width', value);
};
$.fn.cssBorderTopWidth = function(value) {
return this.cssPxValue('border-top-width', value);
};
$.fn.cssBorderWidthY = function(value) {
if (value === undefined) {
return this.cssBorderTopWidth() + this.cssBorderBottomWidth();
}
this.cssBorderTopWidth(value);
this.cssBorderBottomWidth(value);
};
$.fn.cssBorderWidthX = function(value) {
if (value === undefined) {
return this.cssBorderLeftWidth() + this.cssBorderRightWidth();
}
this.cssBorderLeftWidth(value);
this.cssBorderRightWidth(value);
};
/**
* Bottom of a html element without margin and border relative to offset parent. Expects border-box model.
*/
$.fn.innerBottom = function() {
return this.position().top + this.outerHeight(true) - this.cssMarginBottom() - this.cssBorderBottomWidth();
};
/**
* Right of a html element without margin and border relative to offset parent. Expects border-box model.
*/
$.fn.innerRight = function() {
return this.position().left + this.outerWidth(true) - this.cssMarginRight() - this.cssBorderRightWidth();
};
$.fn.copyCss = function($origin, props) {
var properties = props.split(' ');
var $this = this;
properties = explodeShorthandProperties(properties);
properties.forEach(function(prop) {
$this.css(prop, $origin.css(prop));
});
return $this;
};
$.fn.copyCssIfGreater = function($origin, props) {
var properties = props.split(' ');
var $this = this;
properties = explodeShorthandProperties(properties);
properties.forEach(function(prop) {
var originValue = $.pxToNumber($origin.css(prop));
var thisValue = $.pxToNumber($this.css(prop));
if (originValue > thisValue) {
$this.css(prop, originValue + 'px');
}
});
return $this;
};
$.fn.copyCssClasses = function($other, classString) {
var classes = classString.split(' ');
var $this = this;
classes.forEach(function(cssClass) {
if ($other.hasClass(cssClass)) {
$this.addClass(cssClass);
}
});
return $this;
};
$.fn.disableSpellcheck = function() {
return this.attr('spellcheck', false);
};
/**
* Returns whether the current element is the given element or has a child which is the given element.
*/
$.fn.isOrHas = function(elem) {
if (elem instanceof $) {
elem = elem[0];
}
return this[0] === elem || this.has(elem).length > 0;
};
/**
* Makes the current element resizable, which means DIVs for resize-handling are added to the DOM
* in the E, SE and S of the element. This is primarily useful for (modal) dialogs.
*/
$.fn.resizable = function() {
scout.create('Resizable', $(this));
return this;
};
/**
* Makes any element movable with the mouse. If the argument '$handle' is missing, the entire
* element can be used as a handle.
*
* A callback function can be passed as second argument (optional). The function is called for
* every change of the draggable's position with an object as argument:
* { top: (top pixels), left: (left pixels) }
*/
$.fn.draggable = function($handle, callback) {
var $draggable = this;
$handle = $handle || $draggable;
return $handle.on('mousedown.draggable', function(event) {
$('iframe').addClass('dragging-in-progress');
var orig_offset = $draggable.offset();
var orig_event = event;
var handleWidth = $handle.width();
var windowWidth = $handle.window().width();
var windowHeight = $handle.window().height();
$handle.parents()
.on('mousemove.dragging', function(event) {
var top = orig_offset.top + (event.pageY - orig_event.pageY);
var left = orig_offset.left + (event.pageX - orig_event.pageX);
// do not drop outside of viewport (and leave a margin of 100 pixels)
left = Math.max(100 - handleWidth, left);
left = Math.min(windowWidth - 100, left);
top = Math.max(0, top); // must not be dragged outside of top, otherwise dragging back is impossible
top = Math.min(windowHeight - 100, top);
var newOffset = {
top: top,
left: left
};
$draggable.offset(newOffset);
callback && callback(newOffset);
})
.on('mouseup.dragging', function(e) {
$handle.parents().off('.dragging');
$('iframe').removeClass('dragging-in-progress');
});
event.preventDefault();
});
};
/**
* Calls jQuery.fadeOut() and then removes the element from the DOM.
* Default fade-out duration is 150 ms.
*/
$.fn.fadeOutAndRemove = function(duration, callback) {
if (callback === undefined && typeof duration === 'function') {
callback = duration;
duration = undefined;
}
duration = scout.nvl(duration, 150);
return this.stop(true).fadeOut(duration, function() {
$(this).remove();
if (callback) {
callback.call(this);
}
});
};
$.fn.removeAnimated = function(cssClass, callback) {
if (callback === undefined && typeof cssClass === 'function') {
callback = cssClass;
cssClass = undefined;
}
if (this.isDisplayNone()) {
// Remove without animation
this.remove();
callback && callback.call(this);
} else if (!scout.device.supportsCssAnimation()) {
// Cannot remove animated, remove with jQuery.fadeOut()
this.fadeOutAndRemove(callback);
} else {
// Add CSS class and wait for 'animationend' event
this.addClass(cssClass || 'animate-remove');
this.oneAnimationEnd(function() {
$(this).remove();
callback && callback.call(this);
});
}
};
var __origHide = $.fn.hide;
$.fn.hide = function() {
this.trigger('hide');
return __origHide.apply(this, arguments);
};
var __origWidth = $.fn.width;
$.fn.width = function() {
return _ceilNumber(__origWidth.apply(this, arguments));
};
var __origOuterWidth = $.fn.outerWidth;
$.fn.outerWidth = function() {
return _ceilNumber(__origOuterWidth.apply(this, arguments));
};
var __origHeight = $.fn.height;
$.fn.height = function() {
return _ceilNumber(__origHeight.apply(this, arguments));
};
var __origOuterHeight = $.fn.outerHeight;
$.fn.outerHeight = function() {
return _ceilNumber(__origOuterHeight.apply(this, arguments));
};
/**
* This function is required because most jQuery functions can be used with or without arguments
* and do return the jQuery instance when used as a setter (with arguments), ceiling should only
* be done, when used as getter (without arguments).
*/
function _ceilNumber(val) {
return scout.objects.isNumber(val) ? Math.ceil(val) : val;
}
/**
* Sets the given 'text' as text to the jQuery element, using the text() function (i.e. HTML is encoded automatically).
* If the text does not contain any non-space characters, the text '&nbsp;' is set instead (using the html() function).
* If an 'emptyCssClass' is provided, this CSS class is removed in the former and added in the later case.
*/
$.fn.textOrNbsp = function(text, emptyCssClass) {
if (scout.strings.hasText(text)) {
this.text(text);
if (emptyCssClass) {
this.removeClass(emptyCssClass);
}
} else {
this.html('&nbsp;');
if (emptyCssClass) {
this.addClass(emptyCssClass);
}
}
return this;
};
/**
* Same as "textOrNbsp", but with html (caller is responsible for encoding).
*/
$.fn.htmlOrNbsp = function(html, emptyCssClass) {
if (scout.strings.hasText(html)) {
this.html(html);
if (emptyCssClass) {
this.removeClass(emptyCssClass);
}
} else {
this.html('&nbsp;');
if (emptyCssClass) {
this.addClass(emptyCssClass);
}
}
return this;
};
/**
* Like toggleClass(), this toggles a HTML attribute on a set of jquery elements.
*
* @param attr
* Name of the attribute to toggle.
* @param state
* Specifies if the attribute should be added or removed (based on whether the argument is truthy or falsy).
* If this argument is not defined, the attribute is added when it exists, and vice-versa. If this behavior
* is not desired, explicitly cast the argument to a boolean using "!!".
* @param value
* Value to use when adding the attribute.
* If this argument is not specified, 'attr' is used as value.
*/
$.fn.toggleAttr = function(attr, state, value) {
if (!attr) {
return this;
}
if (value === undefined) {
value = attr;
}
return this.each(function() {
var $element = $(this);
if (state === undefined) {
// set state according to the current value
state = ($element.attr(attr) === undefined);
}
if (state) {
// set attr
$element.attr(attr, value);
} else {
// remove attr
$element.removeAttr(attr);
}
});
};
$.fn.backupSelection = function() {
var field = this[0];
if (field && field === this.activeElement(true)) {
return {
selectionStart: field.selectionStart,
selectionEnd: field.selectionEnd,
selectionDirection: field.selectionDirection
};
}
return null;
};
$.fn.restoreSelection = function(selection) {
var field = this[0];
if (field && field === this.activeElement(true) && selection) {
field.setSelectionRange(selection.selectionStart, selection.selectionEnd, selection.selectionDirection);
}
return this;
};
/**
* If the given value is "truthy", it is set as attribute on the target. Otherwise, the attribute is removed.
*/
$.fn.attrOrRemove = function(attributeName, value) {
if (value) {
this.attr(attributeName, value);
} else {
this.removeAttr(attributeName);
}
return this;
};
$.fn.appendAppLink = function(appLinkBean, func) {
return this.appendSpan().appLink(appLinkBean, func);
};
/**
* @param appLinkBean
* Either
* - an AppLinkBean with both (1) a ref attribute which will be mapped to the
* data-ref attribute of the element and (2) a text attribute which will be
* set as the text of the element.
* - or just a ref, which will be mapped to the data-ref attribute of the
* element.
* @param func
* Optional. Either
* - a function to be called when the app link has been clicked
* - or an object with a method named _onAppLinkAction (e.g. an instance of
* BeanField)
* If func is not set, the _onAppLinkAction of the inner most widget relative to
* this element (if any) will be called when the app link has been clicked.
*/
$.fn.appLink = function(appLinkBean, func) {
if (!func) {
func = function(event) {
var widget = scout.widget(this);
if (widget && widget._onAppLinkAction) {
widget._onAppLinkAction(event);
}
}.bind(this);
} else if (typeof func === 'object' && func._onAppLinkAction) {
func = func._onAppLinkAction.bind(func);
}
this.addClass('app-link')
.attr('tabindex', '0')
.unfocusable()
.on('click', func);
if (typeof appLinkBean === 'string') {
this.attr('data-ref', appLinkBean);
} else {
this
.text(appLinkBean.name)
.attr('data-ref', appLinkBean.ref);
}
return this;
};
/**
* Adds the class 'unfocusable' to current result set. The class is not used for styling purposes
* but has a meaning to the FocusManager.
*/
$.fn.unfocusable = function() {
return this.addClass('unfocusable');
};
/**
* Select all text within an element, e.g. within a content editable div element.
*/
$.fn.selectAllText = function() {
var range,
myDocument = this.document(true),
myWindow = this.window(true),
element = this[0];
if (!myDocument || !myDocument.body || !myWindow || !element) {
return this;
}
if (myDocument.body.createTextRange) {
range = myDocument.body.createTextRange();
range.moveToElementText(element);
range.select();
return this;
}
if (myWindow.getSelection) {
range = myDocument.createRange();
range.selectNodeContents(element);
myWindow.getSelection().removeAllRanges();
myWindow.getSelection().addRange(range);
}
return this;
};
$.fn._getClientAndScrollWidthRounded = function() {
var element = this[0];
if (scout.device.isInternetExplorer() || scout.device.isEdge()) {
// IE and Edge seem to round up the scrollWidth. Therefore the clientWidth must be rounded up as well to have a valid comparison.
return {
clientWidth: Math.ceil(element.getBoundingClientRect().width) - this.cssBorderWidthX(), // getBoundingClientRect includes the border -> remove it again to have the clientWidth
scrollWidth: element.scrollWidth
};
}
return {
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth
};
};
$.fn._getClientAndScrollWidthReliable = function() {
var widths = this._getClientAndScrollWidthRounded();
if (!scout.device.isScrollWidthIncludingPadding()) {
// browser supports accurate client- and scroll widths.
return widths;
}
if (widths.scrollWidth > widths.clientWidth) {
// content is large enough so that the scroll-width is already larger than the client-width. Values are correct.
return widths;
}
var paddingRight = this.cssPaddingRight(),
oldStyle = this.attr('style');
if (paddingRight > 0) {
// Some browsers render text within the right-padding (even with overflow=hidden). This has an effect on the value of scrollWidth which may be wrong in these cases (scrollWidth == clientWidth but ellipsis is shown).
// Solution: temporary remove the padding and reduce the width by the padding-size to have the same space for the text but without padding.
this.css({
width: widths.clientWidth - paddingRight,
paddingRight: '0px'
});
widths = this._getClientAndScrollWidthRounded(); // read value again.
this.attrOrRemove('style', oldStyle);
if (widths.scrollWidth > widths.clientWidth) {
return widths;
}
}
// In some cases the browser returns the same values for clientWidth and scrollWidth,
// but will cut off the text nevertheless. At least in FF this seems to be a bug related
// to sub-pixel rendering. The text is "slightly" (0.2 pixels) larger than the clientWidth,
// but scrollWidth returns the same value.
// As a workaround, we do a second measurement of the uncut width before returning false.
var clientWidth = this[0].getBoundingClientRect().width;
this.css('width', 'auto');
this.css('max-width', 'none');
var scrollWidth = this[0].getBoundingClientRect().width;
this.attrOrRemove('style', oldStyle);
return {
clientWidth: clientWidth,
scrollWidth: scrollWidth
};
};
/**
* Checks if content is truncated.
*/
$.fn.isContentTruncated = function() {
var widths = this._getClientAndScrollWidthReliable();
if (widths.scrollWidth > widths.clientWidth) {
return true;
}
};
// TODO [7.0] awe: (graph) consider moving this function to DoubleClickHandler.js
/**
* This function is used to distinct between single and double clicks.
* Instead of executing a handler immediately when the first click occurs,
* we wait for a given timeout (or by default 300 ms) to check if it is followed by a second click.
* This will delay the execution of a single click a bit, so you should use this function wisely.
*/
$.fn.onSingleOrDoubleClick = function(singleClickFunc, doubleClickFunc, timeout) {
return this.each(function() {
var that = this,
numClicks = 0,
timeout = scout.nvl(timeout, 300);
$(this).on('click', function(event) {
numClicks++;
if (numClicks === 1) {
setTimeout(function() {
if (numClicks === 1) {
singleClickFunc.call(that, event);
} else {
doubleClickFunc.call(that, event);
}
numClicks = 0;
}, timeout);
}
});
});
};
$.fn.onPassive = function(eventType, handler) {
var options = scout.events.passiveOptions();
this[0].addEventListener(eventType, handler, options);
return this;
};
$.fn.offPassive = function(eventType, handler) {
var options = scout.events.passiveOptions();
this[0].removeEventListener(eventType, handler, options);
return this;
};
/**
* jquery.binarytransport.js
*
* @description. jQuery ajax transport for making binary data type requests.
* @version 1.0
* @author Henry Algus <henryalgus@gmail.com>
*
* The MIT License (MIT)
*
* Copyright (c) 2014 Henry Algus
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
(function($, undefined) {
// use this transport for "binary" data type
$.ajaxTransport('+binary', function(options, originalOptions, jqXHR) {
// check for conditions and support for blob / arraybuffer response type
if (window.FormData && ((options.dataType && (options.dataType === 'binary')) ||
(options.data && ((window.ArrayBuffer && options.data instanceof ArrayBuffer) ||
(window.Blob && options.data instanceof Blob))))) {
return {
// create new XMLHttpRequest
send: function(headers, callback) {
// setup all variables
var xhr = new XMLHttpRequest(),
url = options.url,
type = options.type,
async = options.async || true,
// blob or arraybuffer. Default is blob
dataType = options.responseType || 'blob',
data = options.data || null,
username = options.username || null,
password = options.password || null;
xhr.addEventListener('load', function() {
var data = {};
data[options.dataType] = xhr.response;
// make callback and send data
callback(xhr.status, xhr.statusText, data, xhr.getAllResponseHeaders());
});
xhr.open(type, url, async, username, password);
// setup custom headers
for (var i in headers) { // NOSONAR
xhr.setRequestHeader(i, headers[i]);
}
// apply custom fields (if provided)
if (options.xhrFields) {
for (var j in options.xhrFields) {
xhr[j] = options.xhrFields[j];
}
}
xhr.responseType = dataType;
xhr.send(data);
},
abort: function() {}
};
}
});
})(window.jQuery);