betajs-browser
Version:
BetaJS-Browser is a client-side JavaScript framework for Browser-specific methods.
4,207 lines • 153 kB
JavaScript
/*!
betajs-browser - v1.0.138 - 2023-03-03
Copyright (c) Oliver Friedmann,Rashad Aliyev
Apache-2.0 Software License.
*/
(function () {
var Scoped = this.subScope();
Scoped.binding('module', 'global:BetaJS.Browser');
Scoped.binding('base', 'global:BetaJS');
Scoped.define("module:", function () {
return {
"guid": "02450b15-9bbf-4be2-b8f6-b483bc015d06",
"version": "1.0.138",
"datetime": 1677885475806
};
});
Scoped.assumeVersion('base:version', '~1.0.104');
Scoped.define("module:Ajax.IframePostmessageAjax", [
"base:Ajax.Support",
"base:Net.Uri",
"base:Net.HttpHeader",
"base:Promise",
"base:Types",
"base:Ajax.RequestException",
"base:Tokens",
"base:Objs"
], function(AjaxSupport, Uri, HttpHeader, Promise, Types, RequestException, Tokens, Objs) {
var id = 1;
var Module = {
supports: function(options) {
if (!options.postmessage)
return false;
return true;
},
execute: function(options) {
var postmessageName = "postmessage_" + Tokens.generate_token() + "_" + (id++);
var params = Objs.objectBy(options.postmessage, postmessageName);
params = Objs.extend(params, options.query);
var uri = Uri.appendUriParams(options.uri, params);
var iframe = document.createElement("iframe");
iframe.id = postmessageName;
iframe.name = postmessageName;
iframe.style.display = "none";
var form = document.createElement("form");
form.method = options.method;
form.target = postmessageName;
uri = AjaxSupport.finalizeUri(options, uri);
form.action = uri;
form.style.display = "none";
var promise = Promise.create();
document.body.appendChild(iframe);
document.body.appendChild(form);
Objs.iter(options.data, function(value, key) {
var input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = Types.is_array(value) || Types.is_object(value) ? JSON.stringify(value) : value;
form.appendChild(input);
}, this);
var post_message_fallback = !("postMessage" in window);
var self = this;
var handle_success = null;
var message_event_handler = function(event) {
handle_success(event.data);
};
handle_success = function(raw_data) {
if (typeof raw_data === "string")
raw_data = JSON.parse(raw_data);
if (!(postmessageName in raw_data))
return;
raw_data = raw_data[postmessageName];
if (post_message_fallback)
window.postMessage = null;
window.removeEventListener("message", message_event_handler, false);
document.body.removeChild(form);
document.body.removeChild(iframe);
AjaxSupport.promiseReturnData(promise, options, raw_data, "json"); //options.decodeType);
};
iframe.onerror = function() {
if (post_message_fallback)
window.postMessage = null;
window.removeEventListener("message", message_event_handler, false);
document.body.removeChild(form);
document.body.removeChild(iframe);
// TODO
//AjaxSupport.promiseRequestException(promise, xmlhttp.status, xmlhttp.statusText, xmlhttp.responseText, "json"); //options.decodeType);)
};
window.addEventListener("message", message_event_handler, false);
if (post_message_fallback)
window.postMessage = handle_success;
form.submit();
return promise;
}
};
AjaxSupport.register(Module, 4);
return Module;
});
Scoped.define("module:Ajax.JsonpScriptAjax", [
"base:Ajax.Support",
"base:Net.Uri",
"base:Net.HttpHeader",
"base:Promise",
"base:Types",
"base:Ajax.RequestException",
"base:Tokens",
"base:Objs",
"base:Async",
"module:Info"
], function(AjaxSupport, Uri, HttpHeader, Promise, Types, RequestException, Tokens, Objs, Async, Info) {
var id = 1;
var Module = {
supports: function(options) {
if (!options.jsonp)
return false;
if (options.method !== "GET")
return false;
return true;
},
execute: function(options) {
var callbackName = "jsonp_" + Tokens.generate_token() + "_" + (id++);
var params = Objs.objectBy(options.jsonp, callbackName);
params = Objs.extend(params, options.query);
params = Objs.extend(params, options.data);
var uri = Uri.appendUriParams(options.uri, params);
var hasResult = false;
window[callbackName] = function(data) {
if (hasResult)
return;
hasResult = true;
try {
delete window[callbackName];
} catch (e) {
window[callbackName] = undefined;
}
AjaxSupport.promiseReturnData(promise, options, data, "json"); //options.decodeType);
};
var promise = Promise.create();
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
var executed = false;
script.onerror = function(event) {
if (event) {
if (event.stopPropagation)
event.stopPropagation();
else
event.cancelBubble = true;
}
if (hasResult)
return;
hasResult = true;
AjaxSupport.promiseRequestException(promise, HttpHeader.HTTP_STATUS_BAD_REQUEST, HttpHeader.format(HttpHeader.HTTP_STATUS_BAD_REQUEST), null, "json"); //options.decodeType);)
};
script.onload = script.onreadystatechange = function() {
if (!executed && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
executed = true;
script.onload = script.onreadystatechange = null;
head.removeChild(script);
if (Info.isInternetExplorer() && Info.internetExplorerVersion() < 9) {
Async.eventually(function() {
if (!hasResult)
script.onerror();
});
}
}
};
uri = AjaxSupport.finalizeUri(options, uri);
script.src = uri;
head.appendChild(script);
return promise;
}
};
AjaxSupport.register(Module, 5);
return Module;
});
Scoped.define("module:Ajax.XDomainRequestAjax", [
"base:Ajax.Support",
"base:Net.Uri",
"base:Net.HttpHeader",
"base:Promise",
"base:Types",
"base:Ajax.RequestException",
"module:Info",
"base:Async",
"base:Ids"
], function(AjaxSupport, Uri, HttpHeader, Promise, Types, RequestException, Info, Async, Ids) {
var Module = {
// IE Garbage Collection for XDomainRequest is broken
__requests: {},
supports: function(options) {
if (!window.XDomainRequest)
return false;
if (options.forceJsonp || options.forcePostmessage)
return false;
if (!options.isCorsRequest)
return false;
if (!Info.isInternetExplorer() || Info.internetExplorerVersion() > 9)
return false;
// TODO: Check Data
return true;
},
execute: function(options) {
var uri = Uri.appendUriParams(options.uri, options.query || {});
if (options.method === "GET")
uri = Uri.appendUriParams(uri, options.data || {});
var promise = Promise.create();
var xdomreq = new XDomainRequest();
Module.__requests[Ids.objectId(xdomreq)] = xdomreq;
xdomreq.onload = function() {
// TODO: Figure out response type.
AjaxSupport.promiseReturnData(promise, options, xdomreq.responseText, "json"); //options.decodeType);
delete Module.__requests[Ids.objectId(xdomreq)];
};
xdomreq.ontimeout = function() {
AjaxSupport.promiseRequestException(promise, HttpHeader.HTTP_STATUS_GATEWAY_TIMEOUT, HttpHeader.format(HttpHeader.HTTP_STATUS_GATEWAY_TIMEOUT), null, "json"); //options.decodeType);)
delete Module.__requests[Ids.objectId(xdomreq)];
};
xdomreq.onerror = function() {
AjaxSupport.promiseRequestException(promise, HttpHeader.HTTP_STATUS_BAD_REQUEST, HttpHeader.format(HttpHeader.HTTP_STATUS_BAD_REQUEST), null, "json"); //options.decodeType);)
delete Module.__requests[Ids.objectId(xdomreq)];
};
uri = AjaxSupport.finalizeUri(options, uri);
xdomreq.open(options.method, uri);
Async.eventually(function() {
if (options.method !== "GET" && !Types.is_empty(options.data)) {
if (options.contentType === "json")
xdomreq.send(JSON.stringify(options.data));
else {
xdomreq.send(Uri.encodeUriParams(options.data, undefined, true));
}
} else
xdomreq.send();
}, this);
return promise;
}
};
AjaxSupport.register(Module, 9);
return Module;
});
Scoped.define("module:Ajax.XmlHttpRequestAjax", [
"base:Ajax.Support",
"base:Net.Uri",
"base:Net.HttpHeader",
"base:Promise",
"base:Types",
"base:Objs",
"base:Ajax.RequestException",
"module:Info"
], function(AjaxSupport, Uri, HttpHeader, Promise, Types, Objs, RequestException, Info) {
var Module = {
supports: function(options) {
// Worker
if (typeof window === "undefined")
return true;
if (!window.XMLHttpRequest)
return false;
if (options.forceJsonp || options.forcePostmessage)
return false;
if (Info.isInternetExplorer() && Info.internetExplorerVersion() < 10 && options.isCorsRequest)
return false;
try {
Objs.iter(options.data, function(value) {
if ((typeof(window.Blob) !== "undefined" && value instanceof(window.Blob)) || (typeof File !== "undefined" && value instanceof File))
options.requireFormData = true;
});
if (options.requireFormData)
new(window.FormData)();
} catch (e) {
options.requireFormData = false;
}
return true;
},
create: function() {
return new XMLHttpRequest();
},
execute: function(options, progress, progressCtx, xmlhttp) {
var uri = Uri.appendUriParams(options.uri, options.query || {});
if (!options.methodSupportsPayload)
uri = Uri.appendUriParams(uri, options.data || {});
var promise = Promise.create();
xmlhttp = xmlhttp || this.create();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState === 4) {
if (HttpHeader.isSuccessStatus(xmlhttp.status) || (xmlhttp.status === 0 && xmlhttp.responseText)) {
AjaxSupport.promiseReturnData(promise, options, options.contentType === "binary" ? xmlhttp.response : (options.contentType === "xml" ? xmlhttp.responseXML : xmlhttp.responseText), options.decodeType || "json");
} else {
AjaxSupport.promiseRequestException(promise, xmlhttp.status, xmlhttp.statusText, xmlhttp.responseText, options.decodeType || "json");
}
}
};
if (progress) {
(xmlhttp.upload || xmlhttp).onprogress = function(e) {
if (e.lengthComputable)
progress.call(progressCtx || this, e.loaded, e.total);
};
}
uri = AjaxSupport.finalizeUri(options, uri);
var parsed = Uri.parse(uri);
if (Info.isFirefox() && parsed.user && parsed.password)
uri = uri.replace(parsed.user + ":" + parsed.password + "@", "");
xmlhttp.open(options.method, uri, true);
if (options.corscreds)
xmlhttp.withCredentials = true;
if (options.bearer)
xmlhttp.setRequestHeader('Authorization', 'Bearer ' + options.bearer);
if (options.accept)
xmlhttp.setRequestHeader("Accept", options.accept);
if (options.contentType === "binary")
xmlhttp.responseType = "blob";
if (parsed.user || parsed.password)
xmlhttp.setRequestHeader('Authorization', 'Basic ' + btoa(parsed.user + ':' + parsed.password));
if (options.methodSupportsPayload && !Types.is_empty(options.data)) {
if (options.noFormData) {
xmlhttp.send(options.data.file);
} else if (options.requireFormData) {
var formData = new(window.FormData)();
Objs.iter(options.data, function(value, key) {
formData.append(key, value);
}, this);
// xmlhttp.setRequestHeader("Content-Type", "multipart/form-data");
xmlhttp.send(formData);
} else if (options.contentType === "json") {
if (options.sendContentType)
xmlhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xmlhttp.send(JSON.stringify(options.data));
} else if (options.contentType === "xml") {
xmlhttp.overrideMimeType('application/xml');
xmlhttp.send(JSON.stringify(options.data));
} else {
if (options.sendContentType)
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xmlhttp.send(Uri.encodeUriParams(options.data, undefined, true));
}
} else
xmlhttp.send();
return promise;
}
};
AjaxSupport.register(Module, 10);
return Module;
});
Scoped.define("module:Apps", [
"base:Time",
"base:Async",
"base:Promise",
"module:Info",
"module:Loader"
], function(Time, Async, Promise, Info, Loader) {
return {
STATE_INCOMPATIBLE_DEVICE: 1,
STATE_APP_LAUNCHED: 2,
STATE_APP_INSTALLED_AND_LAUNCHED: 3,
STATE_APP_NOT_INSTALLED: 4,
STATE_UNKNOWN: 5,
//ios.launch, ios.install, android.intent, android.launch, android.install
launch: function(options) {
var promise = Promise.create();
var start = Time.now();
if (Info.isiOS() && options.ios) {
Async.eventually(function() {
if (Time.now() - start > 3000)
promise.asyncSuccess(this.STATE_APP_LAUNCHED);
else {
start = Time.now();
Async.eventually(function() {
if (Time.now() - start > 3000)
promise.asyncSuccess(this.STATE_APP_INSTALLED_AND_LAUNCHED);
else
promise.asyncError(this.STATE_APP_NOT_INSTALLED);
}, this, 2500);
document.location = options.ios.install;
}
}, this, 2500);
document.location = options.ios.launch;
} else
/*if (Info.isAndroid() && options.android) {
if (Info.isOpera()) {
Loader.loadByIFrame({
url: options.android.launch
}, function () {
document.location
}, this);
} else if (Info.isFirefox()) {
} else {
document.location = options.android.intent;
promise.asyncSuccess(this.STATE_UNKNOWN);
}
} else*/
promise.asyncError(this.STATE_INCOMPATIBLE_DEVICE);
return promise;
},
appStoreLink: function(appIdent) {
return "itms://itunes.apple.com/us/app/" + appIdent + "?mt=8&uo=4";
},
playStoreLink: function(appIdent) {
return "https://play.google.com/store/apps/details?id=<" + appIdent + ">";
},
iOSAppURL: function(protocol, url) {
return protocol + "://" + url;
},
androidAppUrl: function(protocol, url) {
return protocol + "://" + url;
},
googleIntent: function(protocol, url, appIdent) {
return "intent://" + url + ";scheme=" + protocol + ";package=" + appIdent + ";end";
}
};
});
/*
function launchAndroidApp(el) {
heartbeat = setInterval(intervalHeartbeat, 200);
if (navigator.userAgent.match(/Opera/) || navigator.userAgent.match(/OPR/)) {
tryIframeApproach();
} else if (navigator.userAgent.match(/Firefox/)) {
webkitApproach();
iframe_timer = setTimeout(function () {
tryIframeApproach();
}, 1500);
} else if (navigator.userAgent.match(/Chrome/)) {
document.location = googleIntent; // Use google intent
} else { // Native browser ?
document.location = googleIntent; // Use google intent
}
}
function webkitApproach() {
document.location = nativeAndroidUrl;
timer = setTimeout(function () {
document.location = googlePlayStore;
}, 2500);
}
function clearTimers() {
clearTimeout(timer);
clearTimeout(heartbeat);
clearTimeout(iframe_timer);
}
function intervalHeartbeat() {
if (document.webkitHidden || document.hidden) {
clearTimers();
}
}
function tryIframeApproach() {
var iframe = document.createElement("iframe");
iframe.style.border = "none";
iframe.style.width = "1px";
iframe.style.height = "1px";
iframe.onload = function () {
document.location = googlePlayStore;
};
iframe.src = nativeAndroidUrl;
document.body.appendChild(iframe);
}
*/
Scoped.define("module:Blobs", [
"base:Promise"
], function(Promise) {
return {
createBlobByArrayBufferView: function(arrayBuffer, offset, size, type) {
try {
return new(window.Blob)([new DataView(arrayBuffer, offset, size)], {
type: type
});
} catch (err) {
try {
return new(window.Blob)([new Uint8Array(arrayBuffer, offset, size)], {
type: type
});
} catch (err2) {
var BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
var bb = new BlobBuilder();
bb.append(arrayBuffer.slice(offset, offset + size));
return bb.getBlob(type);
}
}
},
loadFileIntoArrayBuffer: function(file) {
var promise = Promise.create();
try {
var fileReader = new FileReader();
fileReader.onloadend = function(ev) {
promise.asyncSuccess(ev.target.result);
};
fileReader.readAsArrayBuffer(file.files ? file.files[0] : file);
} catch (err) {
promise.asyncError(err);
}
return promise;
},
loadFileIntoString: function(file) {
var promise = Promise.create();
try {
var fileReader = new FileReader();
fileReader.onloadend = function(ev) {
promise.asyncSuccess(ev.target.result);
};
fileReader.readAsText(file.files ? file.files[0] : file);
} catch (err) {
promise.asyncError(err);
}
return promise;
}
};
});
Scoped.define("module:Cookies", ["base:Net.Cookies"], function(Cookies) {
return {
get: function(key) {
return Cookies.getCookielikeValue(document.cookie, key);
},
/**
* Will set the Cookie with provided settings
*
* @param {string} key
* @param {string} value
* @param {Date} end
* @param {string} path
* @param {string} domain
* @param {boolean} secure
* @param {'None'|'Lax'|'Strict'} sameSite
*/
set: function(key, value, end, path, domain, secure, sameSite) {
document.cookie = Cookies.createCookielikeValue(key, value, end, path, domain, secure, sameSite);
},
remove: function(key, value, path, domain) {
document.cookie = Cookies.removeCookielikeValue(key, value, path, domain);
},
has: function(key) {
return Cookies.hasCookielikeValue(document.cookie, key);
},
keys: function() {
return Cookies.keysCookielike(document.cookie);
}
};
});
Scoped.define("module:Events", [
"base:Class",
"base:Objs",
"base:Functions",
"module:Dom"
], function(Class, Objs, Functions, Dom, scoped) {
return Class.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function() {
inherited.constructor.call(this);
this.__callbacks = {};
},
destroy: function() {
this.clear();
inherited.destroy.call(this);
},
on: function(element, events, callback, context, options) {
events.split(" ").forEach(function(event) {
if (!event)
return;
var callback_function = Functions.as_method(callback, context || element);
element.addEventListener(event, callback_function, options && Dom.passiveEventsSupported() ? options : false);
this.__callbacks[event] = this.__callbacks[event] || [];
this.__callbacks[event].push({
element: element,
callback_function: callback_function,
callback: callback,
context: context
});
}, this);
return this;
},
off: function(element, events, callback, context) {
events.split(" ").forEach(function(event) {
if (!event)
return;
var entries = this.__callbacks[event];
if (entries) {
var i = 0;
while (i < entries.length) {
var entry = entries[i];
if ((!element || element === entry.element) && (!callback || callback === entry.callback) && (!context || context === entry.context)) {
entry.element.removeEventListener(event, entry.callback_function, false);
entries[i] = entries[entries.length - 1];
entries.pop();
} else
++i;
}
}
}, this);
return this;
},
clear: function() {
Objs.iter(this.__callbacks, function(entries, event) {
entries.forEach(function(entry) {
entry.element.removeEventListener(event, entry.callback_function, false);
});
});
this.__callbacks = {};
}
};
});
});
/*
Copyright (c) Copyright (c) 2007, Carl S. Yestrau All rights reserved.
Code licensed under the BSD License: http://www.featureblend.com/license.txt
Version: 1.0.4
*/
Scoped.define("module:FlashDetect", ["base:Class"], function(Class, scoped) {
return Class.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function() {
inherited.constructor.call(this);
this.__version = null;
if (navigator.plugins && navigator.plugins.length > 0) {
var type = 'application/x-shockwave-flash';
var mimeTypes = navigator.mimeTypes;
if (mimeTypes && mimeTypes[type] && mimeTypes[type].enabledPlugin && mimeTypes[type].enabledPlugin.description)
this.__version = this.parseVersion(mimeTypes[type].enabledPlugin.description);
} else if (navigator.appVersion.indexOf("Mac") == -1 && "execScript" in window) {
for (var i = 0; i < this.__activeXDetectRules.length; i++) {
try {
var obj = new ActiveXObject(this.__activeXDetectRules[i].name);
var version = this.__activeXDetectRules[i].version(obj);
if (version) {
this.__version = this.parseActiveXVersion(version);
break;
}
} catch (err) {}
}
}
},
parseVersion: function(str) {
var descParts = str.split(/ +/);
var majorMinor = descParts[2].split(/\./);
var revisionStr = descParts[3];
return {
"raw": str,
"major": parseInt(majorMinor[0], 10),
"minor": parseInt(majorMinor[1], 10),
"revisionStr": revisionStr,
"revision": parseInt(revisionStr.replace(/[a-zA-Z]/g, ""), 10)
};
},
parseActiveXVersion: function(str) {
var versionArray = str.split(",");
return {
"raw": str,
"major": parseInt(versionArray[0].split(" ")[1], 10),
"minor": parseInt(versionArray[1], 10),
"revision": parseInt(versionArray[2], 10),
"revisionStr": versionArray[2]
};
},
version: function() {
return this.__version;
},
installed: function() {
return this.__version !== null;
},
supported: function() {
var ua = navigator.userAgent;
return this.installed() || !(ua.indexOf('iPhone') != -1 || ua.indexOf('iPod') != -1 || ua.indexOf('iPad') != -1);
},
majorAtLeast: function(version) {
return this.installed() && this.version().major >= version;
},
minorAtLeast: function(version) {
return this.installed() && this.version().minor >= version;
},
revisionAtLeast: function(version) {
return this.installed() && this.version().revision >= version;
},
versionAtLeast: function(major) {
if (!this.installed())
return false;
var properties = [this.version().major, this.version().minor, this.version().revision];
var len = Math.min(properties.length, arguments.length);
for (var i = 0; i < len; i++) {
if (properties[i] != arguments[i])
return properties[i] > arguments[i];
}
return true;
},
__activeXDetectRules: [{
name: "ShockwaveFlash.ShockwaveFlash.7",
version: function(obj) {
try {
return obj.GetVariable("$version");
} catch (err) {
return null;
}
}
}, {
name: "ShockwaveFlash.ShockwaveFlash.6",
version: function(obj) {
try {
obj.AllowScriptAccess = "always";
try {
return obj.GetVariable("$version");
} catch (err) {
return null;
}
} catch (err) {
return "6,0,21";
}
}
}, {
name: "ShockwaveFlash.ShockwaveFlash",
version: function(obj) {
try {
return obj.GetVariable("$version");
} catch (err) {
return null;
}
}
}]
};
});
});
Scoped.define("module:FlashHelper", [
"base:Time", "base:Objs", "base:Types", "base:Net.Uri", "base:Ids", "module:Info", "module:Dom"
], function(Time, Objs, Types, Uri, Ids, Info, Dom) {
return {
getFlashObject: function(container) {
container = Dom.unbox(container);
var embed = container.getElementsByTagName("EMBED")[0];
if (Info.isInternetExplorer() && Info.internetExplorerVersion() <= 10)
embed = null;
if (!embed)
embed = container.getElementsByTagName("OBJECT")[0];
if (!embed) {
var objs = document.getElementsByTagName("OBJECT");
for (var i = 0; i < objs.length; ++i)
if (container.contains(objs[i]))
embed = objs[i];
}
return embed;
},
embedTemplate: function(options) {
options = options || {};
var params = [];
params.push({
"objectKey": "classid",
"value": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
});
params.push({
"objectKey": "codebase",
"value": "https://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab"
});
params.push({
"embedKey": "align",
"value": "middle"
});
params.push({
"embedKey": "play",
"value": "true"
});
params.push({
"embedKey": "loop",
"value": "false"
});
params.push({
"embedKey": "type",
"value": "application/x-shockwave-flash"
});
params.push({
"embedKey": "pluginspage",
"value": "https://get.adobe.com/flashplayer"
});
params.push({
"objectParam": "quality",
"embedKey": "quality",
"value": "high"
});
params.push({
"objectParam": "allowScriptAccess",
"embedKey": "allowScriptAccess",
"value": "always"
});
params.push({
"objectParam": "wmode",
"embedKey": "wmode",
"value": "opaque"
});
params.push({
"objectParam": "movie",
"embedKey": "src",
"value": options.flashFile + (options.forceReload ? "?" + Time.now() : "")
});
if (options.width) {
params.push({
"objectKey": "width",
"embedKey": "width",
"value": options.width
});
}
if (options.height) {
params.push({
"objectKey": "height",
"embedKey": "height",
"value": options.height
});
}
if (options.bgcolor) {
params.push({
"objectParam": "bgcolor",
"embedKey": "bgcolor",
"value": options.bgcolor
});
}
if (options.FlashVars) {
params.push({
"objectParam": "FlashVars",
"embedKey": "FlashVars",
"value": Types.is_object(options.FlashVars) ? Uri.encodeUriParams(options.FlashVars) : options.FlashVars
});
}
params.push({
"objectKey": "id",
"value": options.objectId || Ids.uniqueId("flash")
});
var objectKeys = [];
var objectParams = [];
var embedKeys = [];
Objs.iter(params, function(param) {
if (param.objectKey)
objectKeys.push(param.objectKey + '="' + param.value + '"');
if (param.embedKey)
embedKeys.push(param.embedKey + '="' + param.value + '"');
if (param.objectParam)
objectParams.push('<param name="' + param.objectParam + '" value="' + param.value + '" />');
}, this);
return "<object " + objectKeys.join(" ") + ">" + objectParams.join(" ") + "<embed " + embedKeys.join(" ") + "></embed></object>";
},
embedFlashObject: function(container, options) {
container = Dom.unbox(container);
options = options || {};
if (options.parentBgcolor) {
try {
var hex = container.style.backgroundColor || "";
if (hex.indexOf("rgb") >= 0) {
var rgb = hex.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
var convert = function(x) {
return ("0" + parseInt(x, 10).toString(16)).slice(-2);
};
if (rgb && rgb.length > 3)
hex = "#" + convert(rgb[1]) + convert(rgb[2]) + convert(rgb[3]);
}
options.bgcolor = hex;
} catch (e) {}
}
if (options.fixHalfPixels) {
try {
var offset = Dom.elementOffset(container);
if (offset.top % 1 !== 0)
container.style.marginTop = (Math.round(offset.top) - offset.top) + "px";
if (offset.left % 1 !== 0)
container.style.marginLeft = (Math.round(offset.left) - offset.left) + "px";
} catch (e) {}
}
container.innerHTML = this.embedTemplate(options);
return this.getFlashObject(container);
}
};
});
/*
* Uses modified portions of:
*
* http://www.openjs.com/scripts/events/keyboard_shortcuts/
* Version : 2.01.B
* By Binny V A
* License : BSD
*/
Scoped.define("module:Hotkeys", [
"base:Objs"
], function(Objs) {
return {
SHIFT_NUMS: {
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
";": ":",
"'": "\"",
",": "<",
".": ">",
"/": "?",
"\\": "|"
},
SPECIAL_KEYS: {
'esc': 27,
'escape': 27,
'tab': 9,
'space': 32,
'return': 13,
'enter': 13,
'backspace': 8,
'scrolllock': 145,
'scroll_lock': 145,
'scroll': 145,
'capslock': 20,
'caps_lock': 20,
'caps': 20,
'numlock': 144,
'num_lock': 144,
'num': 144,
'pause': 19,
'break': 19,
'insert': 45,
'home': 36,
'delete': 46,
'end': 35,
'pageup': 33,
'page_up': 33,
'pu': 33,
'pagedown': 34,
'page_down': 34,
'pd': 34,
'left': 37,
'up': 38,
'right': 39,
'down': 40,
'f1': 112,
'f2': 113,
'f3': 114,
'f4': 115,
'f5': 116,
'f6': 117,
'f7': 118,
'f8': 119,
'f9': 120,
'f10': 121,
'f11': 122,
'f12': 123
},
MODIFIERS: ["ctrl", "alt", "shift", "meta"],
keyCodeToCharacter: function(code) {
if (code == 188)
return ",";
else if (code == 190)
return ".";
return String.fromCharCode(code).toLowerCase();
},
handleKeyEvent: function(hotkey, e, options) {
options = Objs.extend({
"disable_in_input": false,
"keycode": false
}, options);
var keys = hotkey.toLowerCase().split("+");
if (options.disable_in_input) {
var element = e.target || e.srcElement || null;
if (element && element.nodeType == 3)
element = element.parentNode;
if (element && (element.tagName == 'INPUT' || element.tagName == 'TEXTAREA'))
return false;
}
var code = e.keyCode || e.which || 0;
var character = this.keyCodeToCharacter(code);
var kp = 0;
var modifier_map = {};
Objs.iter(this.MODIFIERS, function(mod) {
modifier_map[mod] = {
pressed: e[mod + "Key"],
wanted: false
};
}, this);
Objs.iter(keys, function(key) {
if (key in modifier_map) {
modifier_map[key].wanted = true;
kp++;
} else if (key.length > 1) {
if (this.SPECIAL_KEYS[key] == code)
kp++;
} else if (options.keycode) {
if (options.keycode == code)
kp++;
} else if (character == key || (e.shiftKey && this.SHIFT_NUMS[character] == key)) {
kp++;
}
}, this);
/**
* Allow to use use several keys for one action
* @example: ba-hotkey:space^enter="function(){}"
*/
var multipleKeys = hotkey.toLowerCase().split("^");
if (multipleKeys.length > 1) {
Objs.iter(multipleKeys, function(key) {
if (key.length > 1 && e.keyCode === this.SPECIAL_KEYS[key]) {
// Prevent browser scroll when press space
if (code === 32) e.preventDefault();
if (this.SPECIAL_KEYS[key] == code)
kp++;
}
}, this);
}
return kp == keys.length && Objs.all(modifier_map, function(data) {
return data.wanted == data.pressed;
});
},
register: function(hotkey, callback, context, options) {
options = Objs.extend({
"type": "keyup",
"propagate": false,
"disable_in_input": false,
"target": document,
"keycode": false
}, options);
var self = this;
var func = function(e) {
if (self.handleKeyEvent(hotkey, e, options)) {
if (!options.propagate) {
e.preventDefault();
e.stopPropagation();
}
callback.call(context || this, e);
}
};
options.target.addEventListener(options.type, func, false);
return {
target: options.target,
type: options.type,
func: func
};
},
unregister: function(handle) {
handle.target.removeEventListener(handle.type, handle.func, false);
}
};
});
Scoped.define("module:Info", [
"base:Objs", "module:FlashDetect"
], function(Objs, FlashDetect) {
return {
__navigator: null,
getNavigator: function() {
if (!this.__navigator) {
this.__navigator = {
appCodeName: navigator.appCodeName,
appName: navigator.appName,
appVersion: navigator.appVersion,
cookieEnabled: navigator.cookieEnabled,
onLine: navigator.onLine,
platform: navigator.platform,
userAgent: navigator.userAgent,
window_chrome: (typeof window !== "undefined") && ("chrome" in window),
window_opera: (typeof window !== "undefined") && ("opera" in window),
language: navigator.language || navigator.userLanguage || "",
isTouchable: this.isTouchable()
};
}
return this.__navigator;
},
__cache: {},
__cached: function(key, value_func, force) {
if (!(key in this.__cache) || force) {
var n = this.getNavigator();
this.__cache[key] = value_func.call(this, n, n.userAgent, n.userAgent.toLowerCase());
}
return this.__cache[key];
},
setNavigator: function(obj) {
this.__navigator = obj;
this.__cache = {};
},
language: function() {
return this.__cached("language", function(nav) {
return nav.language;
});
},
flash: function(force) {
return this.__cached("flash", function() {
return new FlashDetect();
}, force);
},
isiOS: function() {
return this.__cached("isiOS", function(nav, ua) {
if (this.isInternetExplorer() || this.isIEMobile())
return false;
var strs = [ua];
if (nav.platform)
strs.push(nav.platform);
var ids = ["iPhone", "iPod", "iPad"];
return strs.some(function(s) {
return ids.some(function(i) {
return s.indexOf(i) != -1;
});
}) || (nav.platform && nav.platform === "MacIntel" && nav.isTouchable);
});
},
isEdge: function() {
return this.__cached("isEdge", function(nav, ua) {
return ua.indexOf('Edge') != -1;
});
},
isCordova: function() {
return ((typeof window !== "undefined") && (!!window.cordova || !!window._cordovaNative)) || document.location.href.indexOf("file:///android_asset/www") === 0 || document.location.href.indexOf("file:///var/mobile/Containers/Bundle/Application") === 0;
},
isLocalCordova: function() {
return this.__cached("isLocalCordova", function() {
return this.isCordova() && document.location.href.indexOf("http") !== 0;
});
},
isChrome: function() {
return this.__cached("isChrome", function(nav, ua) {
return (nav.window_chrome || ua.indexOf('CriOS') != -1) && !this.isOpera() && !this.isEdge();
});
},
isChromium: function() {
return this.__cached("isChromium", function(nav, ua, ualc) {
return !this.isChrome() && this.isAndroid() && ualc.indexOf("linux") >= 0;
});
},
isChromiumBased: function() {
return this.__cached("isChromiumBased", function() {
return this.isChrome() || this.isChromium();
});
},
isOpera: function() {
return this.__cached("isOpera", function(nav, ua) {
return nav.window_opera || ua.indexOf(' OPR/') >= 0 || ua.indexOf("OPiOS") >= 0 || ua.indexOf('Opera') >= 0;
});
},
isAndroid: function() {
return this.__cached("isAndroid", function(nav, ua, ualc) {
return ualc.indexOf("android") != -1;
});
},
isWebOS: function() {
return this.__cached("isWebOS", function(nav, ua, ualc) {
return ualc.indexOf("webos") != -1;
});
},
isWindowsPhone: function() {
return this.__cached("isWindowsPhone", function(nav, ua, ualc) {
return ualc.indexOf("windows phone") != -1;
});
},
isBlackberry: function() {
return this.__cached("isBlackberry", function(nav, ua, ualc) {
return ualc.indexOf("blackberry") != -1;
});
},
iOSversion: function() {
return this.__cached("iOSversion", function(nav) {
if (!this.isiOS())
return false;
var v = (nav.appVersion).match(/OS (\d+)_(\d+)_?(\d+)?/);
return {
major: parseInt(v[1], 10),
minor: parseInt(v[2], 10),
revision: parseInt(v[3] || 0, 10)
};
});
},
androidVersion: function() {
return this.__cached("androidVersion", function(nav) {
if (!this.isAndroid())
return false;
var v = (nav.userAgent).match(/Android (\d+)\.(\d+)\.?(\d+)?/);
return {
major: parseInt(v[1], 10),
minor: parseInt(v[2], 10),
revision: parseInt(v[3] || 0, 10)
};
});
},
isMobile: function() {
return this.__cached("isMobile", function() {
return this.isiOS() || this.isAndroid() || this.isWebOS() || this.isWindowsPhone() || this.isBlackberry();
});
},
isDesktop: function() {
return this.__cached("isDesktop", function() {
return !this.isMobile();
});
},
isInternetExplorer: function() {
return this.__cached("isInternetExplorer", function() {
//return navigator.appName == 'Microsoft Internet Explorer';
return !this.isIEMobile() && this.internetExplorerVersion() !== null;
});
},
isIEMobile: function() {
return this.__cached("isIEMobile", function(nav, ua, ualc) {
return ualc.indexOf("iemobile") >= 0;
});
},
isFirefox: function() {
return this.__cached("isFirefox", function(nav, ua, ualc) {
if (ualc.indexOf("firefox") != -1 || ualc.indexOf("fxios") != -1)
return true;
return false;
});
},
isSafari: function() {
return this.__cached("isSafari", function(nav, ua, ualc) {
if (!this.isChrome() && !this.isOpera() && !this.isEdge() && !this.isFirefox() && ualc.indexOf("safari") != -1 && !this.isAndroid())
return true;
if (this.isiOS() && ua.indexOf("Macintosh") != -1 && nav.platform) {
if (nav.platform === "iPad")
return true;
if (nav.isTouchable && nav.platform === "MacIntel")
return true;
}
return false;
});
},
isWindows: function() {
return this.__cached("isWindows", function(nav) {
return nav.appVersion.toLowerCase().indexOf("win") != -1 && !this.isWindowsPhone();
});
},
isMacOS: function() {
return this.__cached("isMacOS", function(nav) {
return !this.isiOS() && nav.appVersion.toLowerCase().indexOf("mac") != -1;
});
},
isUnix: function() {
return this.__cached("isUnix", function(nav) {
return nav.appVersion.toLowerCase().indexOf("x11") != -1;
});
},
isLinux: function() {
return this.__cached("isLinux", function(nav) {
return !this.isAndroid() && nav.appVersion.toLowerCase().indexOf("linux") != -1;
});
},
/**
* Checking if device is touchable device
*
* @return {boolean}
*/
isTouchable: function() {
return (((typeof window !== "undefined") && ('ontouchstart' in window)) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0));
},
/**
* Will return boolean if screen recorder is supported
* We have the same method in betajs-media package, but sometimes it could be required before
* recorder is initialized
* @return {boolean}
*/
isScreenRecorderSupported: function() {
if (typeof navigator.mediaDevices !== 'undefined') {
if ((this.isChrome() || this.isFirefox() || this.isOpera() || this.isEdge()) && !this.isMobile() && typeof navigator.mediaDevices.getDisplayMedia !== 'undefined')
return true;
}
return false;
},
internetExplorerVersion: function() {
return this.__cached("internetExplorerVersion", function(nav, ua) {
if (nav.appName == 'Microsoft Internet Explorer') {
var re = new RegExp("MSIE ([0-9]+)");
var ma = re.exec(ua);
if (ma)
return ma[1];
} else if (nav.appName == 'Netscape') {
var re2 = new RegExp("Trident/.*rv:([0-9]+)");
var ma2 = re2.exec(nav.userAgent);
if (ma2)
return parseFloat(ma2[1]);
}
return null;
});
},
chromeVersion: function() {
return this.__cached("chromeVersion", function(nav, ua) {
var re = /(Chrome|CriOS)\/(\d+\.\d+)[^\d]/gi;
var ma = re.exec(ua);
if (ma)
return parseFloat(ma[2]);
return null;
});
},
operaVersion: function() {
return this.__cached("operaVersion", function(nav, ua) {
var re = /OPR\/(\d+\.\d+)[^\d]/gi;
var ma = re.exec(ua);
if (ma)
return parseFloat(ma[1]);
return null;
});
},
safariVersion: function() {
return this.__cached("safariVersion", function(nav, ua) {
var re = /Version\/(\d+\.\d+)[^\d]/gi;
var ma = re.exec(ua);
if (ma)
return parseFloat(ma[1]);
return null;
});
},
firefoxVersion: function() {
return this.__cached("firefoxVersion", function(nav, ua) {
var re = /Firefox\/(\d+\.\d+)/gi;
var ma = re.exec(ua);
if (ma)
return parseFloat(ma[1]);
return null;
});
},
edgeVersion: function() {
return this.__cached("edgeVersion", function(nav, ua) {
// Edge for the old versions latest was 15, Edg is for Chromium based version
var re = /Edg\/(\d+\.\d+)|Edge\/(\d+\.\d+)/gi;
var ma = re.exec(ua);
if (ma)
return parseFloat(ma[1]);
return null;
});
},
inIframe: function() {
try {
return (typeof window !== "undefined") && (window.self !== window.top);
} catch (e) {
return true;
}
},
__devicesMap: {
mobile: {
format: "Mobile",
check: function() {
return this.isMobile();
}
},
desktop: {
format: "Desktop",
check: function() {
return this.isDesktop();
}
}
},
__obtainMatch: function(map, def) {
var result = null;
Objs.iter(map, function(value, key) {
if (value.check.apply(this)) {
if (result) {
result = null;
return false;
}
result = Objs.clone(value, 1);
delete result.check;
result.key = key;
}
}, this);
return result || def;
},
getDevice: function() {
return this.__cached("getDevice", function() {
return this.__obtainMatch(this.__devicesMap, {
key: "unknown",
format: "Unknown Device"
});
});
},
formatDevice: function() {
return this.getDevice().format;
},
__osMap: {
macosx: {
format: "Mac OS-X",
check: function() {
return this.isMacOS();
}
},
windows: {
format: "Windows",
check: function() {
return this.isWindows();
}
},
unix: {
format: "Unix",
check: function() {
return this.isUnix();
}
},
linux: {
format: "Linux",
check: function() {
return this.isLinux();
}
},
ios: {
format: "iOS",
check: function() {
return this.isiOS();
},
version: function() {
return this.iOSversion().major + "." + this.iOSversion().minor + "." + this.iOSversion().revision;
}
},
android: {
format: "Android",
check: function() {
return this.isAndroid();
}
},
webos: {
format: "WebOS",
check: function() {
return this.isWebOS();
}
},
windowsphone: {
format: "Windows Phone",
check: function() {
return this.isWindowsPhone();
}
},
blackberry: {
format: "Blackberry",
check: function() {
return this.isBlackberry();
}
}
},
getOS: function() {
return this.__cached("getOS", function() {
return this.__obtainMatch(this.__osMap, {
key: "unknown",
format: "Unknown Operating System"
});
});
},
formatOS: function() {
return this.getOS().format;
},
formatOSVersion: function() {
return this.getOS().version ? this.getOS().version.apply(this) : "";
},
__browserMap: {
chrome: {
format: "Chrome",
check: function() {
return this.isChrome();
},
version: function() {
return this.chromeVersion();
}
},
chromium: {
format: "Chromium",
check: function() {
return this.isChromium();
}
},
opera: {
format: "Opera",
check: function() {
return this.isOpera();
},
version: function() {
return this.operaVersion();
}
},
internetexplorer: {
format: "Internet Explorer",
check: function() {
return this.isInternetExplorer();
},
version: function() {
return this.internetExplorerVersion();
}
},
firefox: {
format: "Firefox",
check: function() {
return this.isFirefox();
},
version: function() {
return this.firefoxVersion();
}
},
safari: {
format: "Safari",
check: function() {
return this.isSafari();
},
version: function() {
return this.safariVersion();
}
},
webos: {
format: "WebOS",
check: function() {
return this.isWebOS();
}
},
blackberry: {
format: "Blackberry",
check: function() {
return this.isBlackberry();
}
},
edge: {
format: "Edge",
check: function() {
return this.isEdge();
}
},
iemobile: {
format: "IE Mobile",
check: function() {
return this.isIEMobile();
}
}
},
getBrowser: function() {
return this.__cached("getBrowser", function() {
return this.__obtainMatch(this.__browserMap, {
key: "unknown",
format: "Unknown Browser"
});
});
},
formatBrowser: function() {
return this.getBrowser().format;
},
formatBrowserVersion: function() {
return this.getBrowser().version ? this.getBrowser().version.apply(this) : "";
},
formatFlash: function() {
return this.flash().installed() ?
("Flash " + this.flash().version().raw) :
(this.flash().supported() ?
"Flash not installed but supported" :
"Flash not supported");
}
};
});
Scoped.define("module:Loader", [
"base:Ajax.Support",
"module:Info"
], function(AjaxSupport, Info) {
return {
loadScript: function(url, callback, context) {
var executed = false;
var head = document.getElementsByTagName("head")[0];
var script = document.createElement("script");
script.src = url;
script.onload = script.onreadystatechange = function() {
if (!executed && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
executed = true;
script.onload = script.onreadystatechange = null;
if (callback)
callback.call(context || this, url);
// Does not work properly if we remove the script for some reason if it is used the second time !?
//head.removeChild(script);
}
};
head.appendChild(script);
},
loadStyles: function(url, callback, context) {
var executed = false;
var head = document.getElementsByTagName("head")[0];
var style = document.createElement("link");
style.rel = "stylesheet";
style.href = url;
style.onload = style.onreadystatechange = function() {
if (!executed && (!this.readyState || this.readyState == "loaded" || this.readyState == "complete")) {
executed = true;
style.onload = style.onreadystatechange = null;
if (callback)
callback.call(context || this, url);
}
};
head.appendChild(style);
},
inlineStyles: function(styles) {
var head = document.getElementsByTagName("head")[0];
var style = document.createElement("style");
if (Info.isInternetExplorer() && Info.internetExplorerVersion() < 9) {
style.setAttribute('type', 'text/css');
style.styleSheet.cssText = styles;
} else
style.textContent = styles;
head.appendChild(style);
return style;
},
loadHtml: function(uri, callback, context) {
AjaxSupport.execute({
uri: uri,
decodeType: "html"
}).success(function(content) {
callback.call(this, content, uri);
}, context);
},
findScript: function(substr) {
for (var i = 0; i < document.scripts.length; ++i)
if (document.scripts[i].src.toLowerCase().indexOf(substr.toLowerCase()) >= 0)
return document.scripts[i];
return null;
},
loadByIframe: function(options, callback, context) {
var iframe = document.createElement("iframe");
if (options.visible) {
iframe.style.border = "none";
iframe.style.width = "1px";
iframe.style.height = "1px";
} else {
iframe.style.display = "none";
}
var loaded = function() {
var body = null;
var content = null;
try {
body = iframe.contentDocument.body;
content = body.textContent || body.innerText;
} catch (e) {}
callback.call(context || this, content, body, iframe);
if (options.remove)
document.body.removeChild(iframe);
};
if (iframe.attachEvent)
iframe.attachEvent("onload", loaded);
else
iframe.onload = loaded;
iframe.src = options.url;
document.body.appendChild(iframe);
}
};
});
Scoped.define("module:HashRouteBinder", [
"base:Router.RouteBinder",
"module:Events"
], function(RouteBinder, Events, scoped) {
return RouteBinder.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(router) {
inherited.constructor.call(this, router);
var events = this.auto_destroy(new Events());
events.on(window, "hashchange", function() {
this._localRouteChanged();
}, this);
},
_getLocalRoute: function() {
var hash = window.location.hash;
return (hash.length && hash[0] == '#') ? hash.slice(1) : hash;
},
_setLocalRoute: function(currentRoute) {
window.location.hash = "#" + currentRoute.route;
}
};
});
});
Scoped.define("module:HistoryRouteBinder", [
"base:Router.RouteBinder",
"module:Events"
], function(RouteBinder, Events, scoped) {
return RouteBinder.extend({
scoped: scoped
}, function(inherited) {
return {
__used: false,
constructor: function(router) {
inherited.constructor.call(this, router);
var events = this.auto_destroy(new Events());
events.on(window, "hashchange", function() {
if (this.__used)
this._localRouteChanged();
}, this);
},
_getLocalRoute: function() {
return window.location.pathname;
},
_setLocalRoute: function(currentRoute) {
window.history.pushState({}, document.title, currentRoute.route);
this.__used = true;
}
};
}, {
supported: function() {
return window.history && window.history.pushState;
}
});
});
Scoped.define("module:LocationRouteBinder", [
"base:Router.RouteBinder"
], function(RouteBinder, scoped) {
return RouteBinder.extend({
scoped: scoped
}, {
_getLocalRoute: function() {
return window.location.pathname;
},
_setLocalRoute: function(currentRoute) {
window.location.pathname = currentRoute.route;
}
});
});
Scoped.define("module:DomExtend.DomExtension", [
"base:Class",
"base:Objs",
"base:Functions",
"base:Async",
"module:Dom",
"module:DomMutation.NodeRemoveObserver",
"module:DomMutation.NodeResizeObserver"
], function(Class, Objs, Functions, Async, Dom, NodeRemoveObserver, NodeResizeObserver, scoped) {
return Class.extend({
scoped: scoped
}, function(inherited) {
return {
_domMethods: [],
_domAttrs: {},
constructor: function(element, attrs) {
inherited.constructor.call(this);
this._element = Dom.unbox(element);
this._element.domExtension = this;
this._actualBB = null;
this._idealBB = null;
this._attrs = attrs || {};
Objs.iter(this._domMethods, function(method) {
this._element[method] = Functions.as_method(this[method], this);
}, this);
Objs.iter(['get', 'set'], function(method) {
this._element[method] = Functions.as_method(this[method], this);
}, this);
Async.eventually(function() {
this._nodeRemoveObserver = this.auto_destroy(new NodeRemoveObserver(this._element));
this._nodeRemoveObserver.on("node-removed", this.weakDestroy, this);
this._nodeResizeObserver = this.auto_destroy(new NodeResizeObserver(this._element));
this._nodeResizeObserver.on("node-resized", function() {
this.recomputeBB();
this._notify("resized");
}, this);
}, this);
if (!this._element.style.display || this._element.style.display == "inline")
this._element.style.display = "inline-block";
},
domEvent: function(eventName) {
Dom.triggerDomEvent(this._element, eventName);
},
readAttr: function(key) {
return key in this._element.attributes ? this._element.attributes[key].value : (key in this._element ? this._element[key] : this._attrs[key]);
},
hasAttr: function(key) {
return key in this._element.attributes || key in this._element || key in this._attrs;
},
writeAttr: function(key, value) {
if (key in this._element.attributes)
this._element.attributes[key].value = value;
else if (key in this._element)
this._element[key] = value;
else
this._attrs[key] = value;
},
unsetAttr: function(key) {
delete this._element[key];
this._element.removeAttribute(key);
delete this._attrs[key];
},
get: function(key) {
var meta = this._domAttrs[key] || {};
if (!(meta.get))
return this.readAttr(key);
var value = Functions.callWithin(this, meta.get);
this.writeAttr(key, value);
return value;
},
set: function(key, value) {
this.writeAttr(key, value);
var meta = this._domAttrs[key] || {};
if (meta.set)
Functions.callWithin(this, meta.set, value);
},
computeActualBB: function(idealBB) {
var width = Dom.elementDimensions(this._element).width;
//var height = Dom.elementDimensions(this._element).height;
if (width < idealBB.width && !this._element.style.width) {
this._element.style.width = idealBB.width + "px";
width = Dom.elementDimensions(this._element).width;
var current = this._element;
while (current != document.body) {
current = current.parentNode;
width = Math.min(width, Dom.elementDimensions(current).width);
}
this._element.style.width = null;
}
/*
if (height < idealBB.height && !this._element.style.height) {
this._element.style.height = idealBB.height + "px";
height = Dom.elementDimensions(this._element).height;
var current = this._element;
while (current != document) {
current = current.parentNode;
height = Math.min(height, Dom.elementDimensions(current).height);
}
this._element.style.height = null;
}
var arWidth = Math.round(height * idealBB.width / idealBB.height);
var arHeight = Math.round(width * idealBB.height / idealBB.width);
return {
width: Math.min(width, arWidth),
height: Math.min(height, arHeight)
};
*/
return {
width: width,
height: width * idealBB.height / idealBB.width
};
},
idealBB: function() {
return null;
},
recomputeBB: function() {
var idealBB = this.idealBB();
if (!idealBB)
return;
var actualBB = this.computeActualBB(idealBB);
this._idealBB = idealBB;
this._actualBB = actualBB;
this.setActualBB(actualBB);
},
setActualBB: function(actualBB) {}
};
});
});
Scoped.define("module:DomMutation.NodeRemoveObserver", [
"base:Classes.ConditionalInstance",
"base:Events.EventsMixin"
], function(ConditionalInstance, EventsMixin, scoped) {
return ConditionalInstance.extend({
scoped: scoped
}, [EventsMixin, function(inherited) {
return {
constructor: function(node) {
inherited.constructor.call(this);
this._node = node;
},
_nodeRemoved: function(node) {
if (node !== this._node)
return;
this.trigger("node-removed");
}
};
}]);
});
Scoped.define("module:DomMutation.MutationObserverNodeRemoveObserver", [
"module:DomMutation.NodeRemoveObserver",
"base:Objs"
], function(Observer, Objs, scoped) {
return Observer.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(node) {
inherited.constructor.call(this, node);
var self = this;
this._observer = new window.MutationObserver(function(mutations) {
Objs.iter(mutations, function(mutation) {
for (var i = 0; i < mutation.removedNodes.length; ++i)
self._nodeRemoved(mutation.removedNodes[i]);
});
});
this._observer.observe(node.parentNode, {
childList: true
});
},
destroy: function() {
this._observer.disconnect();
inherited.destroy.call(this);
}
};
}, {
supported: function(node) {
try {
return !!window.MutationObserver;
} catch (e) {
return false;
}
}
});
});
Scoped.define("module:DomMutation.DOMNodeRemovedNodeRemoveObserver", [
"module:DomMutation.NodeRemoveObserver",
"module:Info",
"module:Events"
], function(Observer, Info, Events, scoped) {
return Observer.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(node) {
inherited.constructor.call(this, node);
var events = this.auto_destroy(new Events());
events.on(document, "DOMNodeRemoved", function(event) {
this._nodeRemoved(event.target);
}, this);
}
};
}, {
supported: function(node) {
return !Info.isInternetExplorer() || Info.internetExplorerVersion() >= 9;
}
});
});
Scoped.define("module:DomMutation.TimerNodeRemoveObserver", [
"module:DomMutation.NodeRemoveObserver",
"base:Timers.Timer"
], function(Observer, Timer, scoped) {
return Observer.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(node) {
inherited.constructor.call(this, node);
this._timer = new Timer({
context: this,
fire: this._fire,
delay: 100
});
},
destroy: function() {
this._timer.weakDestroy();
inherited.destroy.call(this);
},
_fire: function() {
if (!this._node.parentElement) {
this._timer.stop();
this._nodeRemoved(this._node);
}
}
};
}, {
supported: function(node) {
return true;
}
});
});
Scoped.extend("module:DomMutation.NodeRemoveObserver", [
"module:DomMutation.NodeRemoveObserver",
"module:DomMutation.MutationObserverNodeRemoveObserver",
"module:DomMutation.DOMNodeRemovedNodeRemoveObserver",
"module:DomMutation.TimerNodeRemoveObserver"
], function(Observer, MutationObserverNodeRemoveObserver, DOMNodeRemovedNodeRemoveObserver, TimerNodeRemoveObserver) {
Observer.register(MutationObserverNodeRemoveObserver, 3);
Observer.register(DOMNodeRemovedNodeRemoveObserver, 2);
Observer.register(TimerNodeRemoveObserver, 1);
return {};
});
Scoped.define("module:DomMutation.NodeResizeObserver", [
"base:Class",
"base:Events.EventsMixin",
"module:Events"
], function(Class, EventsMixin, Events, scoped) {
return Class.extend({
scoped: scoped
}, [EventsMixin, function(inherited) {
return {
constructor: function(node) {
inherited.constructor.call(this);
var events = this.auto_destroy(new Events());
events.on(window, "resize", function(event) {
this._resized();
}, this);
},
_resized: function() {
this.trigger("node-resized");
}
};
}]);
});
Scoped.define("module:DomMutation.NodeInsertObserver", [
"base:Classes.ConditionalInstance",
"base:Events.EventsMixin"
], function(ConditionalInstance, EventsMixin, scoped) {
return ConditionalInstance.extend({
scoped: scoped
}, [EventsMixin, function(inherited) {
return {
_nodeInserted: function(node, expand) {
if (expand) {
for (var i = 0; i < node.childNodes.length; ++i)
this._nodeInserted(node.childNodes[i], expand);
}
if (this._options.parent && node.parentNode !== this._options.parent)
return;
if (this._options.root && !this._options.root.contains(node))
return;
if (this._options.filter && !this._options.filter.call(this._options.context || this, node))
return;
this.trigger("node-inserted", node);
}
};
}]);
});
Scoped.define("module:DomMutation.MutationObserverNodeInsertObserver", [
"module:DomMutation.NodeInsertObserver",
"base:Objs"
], function(Observer, Objs, scoped) {
return Observer.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(options) {
options = options || {};
inherited.constructor.call(this, options);
var self = this;
this._observer = new window.MutationObserver(function(mutations) {
Objs.iter(mutations, function(mutation) {
for (var i = 0; i < mutation.addedNodes.length; ++i)
self._nodeInserted(mutation.addedNodes[i], true);
});
});
this._observer.observe(this._options.root || this._options.parent || document.body, {
childList: true,
subtree: !this._options.parent
});
},
destroy: function() {
this._observer.disconnect();
inherited.destroy.call(this);
}
};
}, {
supported: function(node) {
try {
return !!window.MutationObserver;
} catch (e) {
return false;
}
}
});
});
Scoped.define("module:DomMutation.DOMNodeInsertedNodeInsertObserver", [
"module:DomMutation.NodeInsertObserver",
"module:Events"
], function(Observer, Events, scoped) {
return Observer.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(options) {
options = options || {};
inherited.constructor.call(this, options);
var events = this.auto_destroy(new Events());
events.on(document, "DOMNodeInserted", function(event) {
this._nodeInserted(event.target, true);
}, this);
}
};
}, {
supported: function(node) {
return true;
}
});
});
Scoped.extend("module:DomMutation.NodeInsertObserver", [
"module:DomMutation.NodeInsertObserver",
"module:DomMutation.MutationObserverNodeInsertObserver",
"module:DomMutation.DOMNodeInsertedNodeInsertObserver"
], function(Observer, MutationObserverNodeInsertObserver, DOMNodeInsertedNodeInsertObserver) {
Observer.register(MutationObserverNodeInsertObserver, 3);
Observer.register(DOMNodeInsertedNodeInsertObserver, 2);
return {};
});
Scoped.define("module:Dom", [
"base:Types",
"base:Objs",
"module:Info",
"base:Async"
], function(Types, Objs, Info, Async) {
var TEMPLATE_TAG_MAP = {
"tr": ["table", "tbody"],
"td": ["table", "tbody", "tr"],
"th": ["table", "thead", "tr"]
};
var INTERACTION_EVENTS = ["click", "mousedown", "mouseup", "touchstart", "touchend", "keydown", "keyup", "keypress"];
var userInteractionCallbackList = [];
var userInteractionCallbackWaiting = false;
var userInteractionCallbackFunc = function() {
userInteractionCallbackList.forEach(function(entry) {
entry.callback.call(entry.context || this);
});
userInteractionCallbackList = [];
userInteractionCallbackWaiting = false;
INTERACTION_EVENTS.forEach(function(event) {
window.removeEventListener(event, userInteractionCallbackFunc);
});
};
var Dom = {
ready: function(callback, context) {
if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll)) {
Async.eventually(callback, context);
} else {
var completed;
var done = false;
var timer = null;
completed = function() {
clearInterval(timer);
document.removeEventListener("DOMContentLoaded", completed);
window.removeEventListener("load", completed);
if (done)
return;
done = true;
callback.apply(context || this);
};
document.addEventListener("DOMContentLoaded", completed);
window.addEventListener("load", completed);
timer = setInterval(function() {
if (document.readyState === "complete" || (document.readyState !== "loading" && !document.documentElement.doScroll))
completed();
}, 10);
}
},
userInteraction: function(callback, context) {
userInteractionCallbackList.push({
callback: callback,
context: context
});
if (!userInteractionCallbackWaiting) {
userInteractionCallbackWaiting = true;
INTERACTION_EVENTS.forEach(function(event) {
window.addEventListener(event, userInteractionCallbackFunc);
});
}
},
isTabHidden: function() {
return document.hidden || document.webkitHidden || document.mozHidden || document.msHidden;
},
elementsByTemplate: function(template) {
template = template.trim();
var polyfill = Info.isInternetExplorer() && Info.internetExplorerVersion() < 9;
/*
* TODO: This is probably not a good fix.
*
* Some tags, like tr, are not generated by the browser when under a generic tag like div.
* In other words
*
* <div>.innerHTML = "<tr><p>foo</p></tr>" will become <div><p>foo</p></div>
*
* The quick fix here checks for an outer tag and picks the proper temporary parent tag.
*
* This needs to be fixed properly in the future.
*/
var parentTags = [];
Objs.iter(TEMPLATE_TAG_MAP, function(value, key) {
if (template.indexOf("<" + key) === 0) {
parentTags = value;
polyfill = false;
}
});
var outerTemplate = [
parentTags.map(function(t) {
return "<" + t + ">";
}).join(""),
polyfill ? "<br/>" : "",
template,
parentTags.map(function(t) {
return "</" + t + ">";
}).join("")
].join("");
var element = document.createElement("div");
element.innerHTML = outerTemplate;
parentTags.forEach(function() {
element = element.children[0];
});
var result = [];
for (var i = polyfill ? 1 : 0; i < element.children.length; ++i)
result.push(element.children[i]);
if (polyfill) {
result = result.filter(function(el) {
return !(el.tagName.indexOf("/") === 0 && template.toLowerCase().indexOf("<" + el.tagName.toLowerCase()) >= 0);
});
}
return result;
},
elementByTemplate: function(template, encapsulate_in_div_if_needed) {
var result = this.elementsByTemplate(template);
if (result.length === 1)
return result[0];
if (result.length === 0 || !encapsulate_in_div_if_needed)
return null;
var element = document.createElement("div");
result.forEach(element.appendChild, element);
return element;
},
changeTag: function(node, name) {
var replacement = document.createElement(name);
for (var i = 0; i < node.attributes.length; ++i) {
var attr = node.attributes[i];
replacement.setAttribute(attr.nodeName, "value" in attr ? attr.value : attr.nodeValue);
}
while (node.firstChild)
replacement.appendChild(node.firstChild);
if (node.parentNode)
node.parentNode.replaceChild(replacement, node);
return replacement;
},
traverseNext: function(node, skip_children) {
node = this.unbox(node);
if (node.firstChild && !skip_children)
return node.firstChild;
if (!node.parentNode)
return null;
if (node.nextSibling)
return node.nextSibling;
return this.traverseNext(node.parentNode, true);
},
splitNode: function(node, start_offset, end_offset) {
start_offset = start_offset || 0;
end_offset = end_offset || (node.wholeText ? node.wholeText.length : 0);
if (end_offset < (node.wholeText ? node.wholeText.length : 0))
node.splitText(end_offset);
if (start_offset > 0)
node = node.splitText(start_offset);
return node;
},
__FULLSCREEN_EVENTS: ["fullscreenchange", "webkitfullscreenchange", "mozfullscreenchange", "MSFullscreenChange"],
__FULLSCREEN_METHODS: ["requestFullscreen", "webkitRequestFullscreen", "mozRequestFullScreen", "msRequestFullscreen", "webkitEnterFullScreen"],
__FULLSCREEN_ATTRS: ["fullscreenElement", "webkitFullscreenElement", "mozFullScreenElement", "msFullscreenElement"],
__FULLSCREEN_EXIT_METHODS: ["exitFullscreen", "mozCancelFullScreen", "webkitExitFullscreen", "msExitFullscreen"],
elementSupportsFullscreen: function(element) {
return element && this.__FULLSCREEN_METHODS.some(function(key) {
return key in element;
});
},
elementEnterFullscreen: function(element) {
var done = false;
this.__FULLSCREEN_METHODS.forEach(function(key) {
if (!done && (key in element)) {
element[key].call(element);
done = true;
}
});
},
// Will exit from document's full screen mode
documentExitFullscreen: function() {
this.__FULLSCREEN_EXIT_METHODS.forEach(function(key) {
if (document[key]) {
document[key]();
}
});
},
elementIsFullscreen: function(element) {
return this.__FULLSCREEN_ATTRS.some(function(key) {
return document[key] === element;
});
},
elementOnFullscreenChange: function(element, callback, context) {
var self = this;
var listener = function() {
callback.call(context || this, element, self.elementIsFullscreen(element));
};
this.__FULLSCREEN_EVENTS.forEach(function(event) {
element.addEventListener(event, listener, false);
});
return listener;
},
elementOffFullscreenChange: function(element, listener) {
this.__FULLSCREEN_EVENTS.forEach(function(event) {
element.removeEventListener(event, listener, false);
});
},
__PIP_EVENTS: ["enterpictureinpicture", "leavepictureinpicture"],
/**
* If browser supports picture-in-picture
*
* INFO: to enable PIP in FF: about:config, set media.videocontrols.picture-in-picture.enabled,
* media.videocontrols.picture-in-picture.video-toggle.enabled;true
* and media.videocontrols.picture-in-picture.video-toggle.flyout-enabled
*
* @param {HTMLVideoElement =} videoElement
* @returns {boolean}
*/
browserSupportsPIP: function(videoElement) {
videoElement = videoElement || HTMLVideoElement.prototype || {};
if ('pictureInPictureEnabled' in document)
return true;
if (Info.isMacOS() && Info.safariVersion() >= 9)
return !!(videoElement.webkitSupportsPresentationMode && typeof videoElement.webkitSetPresentationMode === "function");
return false;
},
/**
*
* @param {HTMLVideoElement} videoElement
*/
videoElementEnterPIPMode: function(videoElement) {
if (!videoElement || !(videoElement instanceof HTMLVideoElement))
return;
videoElement = videoElement || HTMLVideoElement.prototype || {};
if ('pictureInPictureElement' in document) {
if (!document.pictureInPictureElement && typeof videoElement.requestPictureInPicture === 'function') {
try {
videoElement.requestPictureInPicture();
} catch (err) {
console.warn(err);
}
}
}
if (Info.isMacOS() && Info.safariVersion() >= 9 && typeof videoElement.webkitSetPresentationMode === 'function')
videoElement.webkitSetPresentationMode("picture-in-picture");
},
/**
* Video Will Exit From PIP Mode
* @param {HTMLVideoElement} videoElement
*/
videoElementExitPIPMode: function(videoElement) {
videoElement = videoElement || HTMLVideoElement.prototype || {};
if ('pictureInPictureElement' in document) {
if (document.pictureInPictureElement && typeof document.exitPictureInPicture === 'function') {
try {
document.exitPictureInPicture();
} catch (err) {
console.warn(err);
}
}
}
if (Info.isMacOS() && Info.safariVersion() >= 9 && typeof videoElement.webkitSetPresentationMode === 'function') {
videoElement.webkitSetPresentationMode('inline');
}
},
/**
* Will check if Video Element in PIP Mode
* @param {HTMLVideoElement} videoElement
* @returns {boolean}
*/
videoIsInPIPMode: function(videoElement) {
if ('pictureInPictureElement' in document)
return !!document.pictureInPictureElement;
if (Info.isMacOS() && Info.safariVersion() >= 9)
return (videoElement.webkitPresentationMode === "picture-in-picture");
},
/**
*
* @param {HTMLVideoElement} videoElement
* @param {function} callback
* @param {object =} context
* @returns {listener | null}
*/
videoAddPIPChangeListeners: function(videoElement, callback, context) {
if (!videoElement || !(videoElement instanceof HTMLVideoElement))
return null;
var self = this;
var listener = function() {
callback.call(context || this, videoElement, self.videoIsInPIPMode(videoElement));
};
this.__PIP_EVENTS.forEach(function(event) {
videoElement.addEventListener(event, listener, false);
});
return listener;
},
/**
*
* @param {HTMLVideoElement} videoElement
* @param {function} callback
* @param {object =} context
* @returns {listener}
*/
videoRemovePIPChangeListeners: function(videoElement) {
if (!videoElement || !(videoElement instanceof HTMLVideoElement))
return null;
this.__PIP_EVENTS.forEach(function(event) {
videoElement.removeEventListener(event, videoElement);
});
},
entitiesToUnicode: function(s) {
if (!s || !Types.is_string(s) || s.indexOf("&") < 0)
return s;
return s.split(">").map(function(s) {
return s.split("<").map(function(s) {
var temp = document.createElement("span");
temp.innerHTML = s;
s = temp.textContent || temp.innerText;
if (temp.remove)
temp.remove();
return s;
}).join("<");
}).join(">");
},
unbox: function(element) {
if (Types.is_string(element))
element = document.querySelector(element);
return !element || element.nodeType ? element : element.get(0);
},
triggerDomEvent: function(element, eventName, parameters, customEventParams) {
element = this.unbox(element);
eventName = eventName.toLowerCase();
var onEvent = "on" + eventName;
var onEventHandler = null;
var onEventCalled = false;
if (element[onEvent]) {
onEventHandler = element[onEvent];
element[onEvent] = function() {
if (onEventCalled)
return;
onEventCalled = true;
onEventHandler.apply(this, arguments);
};
}
try {
var event;
try {
if (customEventParams)
event = new CustomEvent(eventName, customEventParams);
else
event = new Event(eventName);
} catch (e) {
try {
if (customEventParams) {
event = document.createEvent('CustomEvent');
event.initCustomEvent(eventName, customEventParams.bubbles || false, customEventParams.cancelable || false, customEventParams.detail || false);
} else {
event = document.createEvent('Event');
event.initEvent(eventName, false, false);
}
} catch (e) {
event = document.createEventObject();
event.type = eventName;
}
}
Objs.extend(event, parameters);
element.dispatchEvent(event);
if (onEventHandler) {
if (!onEventCalled)
onEventHandler.call(element, event);
element[onEvent] = onEventHandler;
}
} catch (e) {
if (onEventHandler)
element[onEvent] = onEventHandler;
throw e;
}
},
elementOffset: function(element) {
element = this.unbox(element);
var top = 0;
var left = 0;
if (element.getBoundingClientRect) {
var box = element.getBoundingClientRect();
top = box.top;
left = box.left - (document.body.getBoundingClientRect ? document.body.getBoundingClientRect().left : 0);
}
docElem = document.documentElement;
return {
top: top + (window.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0),
left: left + (window.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0)
};
},
getRelativeCoordinates: function(event, element) {
var position = {
x: event.pageX,
y: event.pageY
};
var offset = {
left: element.offsetLeft,
top: element.offsetTop
};
var reference = element.offsetParent;
while (reference != null) {
offset.left += reference.offsetLeft;
offset.top += reference.offsetTop;
reference = reference.offsetParent;
}
return {
x: position.x - offset.left,
y: position.y - offset.top
};
},
elementDimensions: function(element) {
element = this.unbox(element);
var cs, w, h;
if (element && window.getComputedStyle) {
try {
cs = window.getComputedStyle(element);
} catch (e) {}
if (cs) {
w = parseInt(cs.width, 10);
h = parseInt(cs.height, 10);
if (w && h) {
return {
width: w,
height: h
};
}
}
}
if (element && element.currentStyle) {
cs = element.currentStyle;
w = element.clientWidth - parseInt(cs.paddingLeft || 0, 10) - parseInt(cs.paddingRight || 0, 10);
h = element.clientHeight - parseInt(cs.paddingTop || 0, 10) - parseInt(cs.paddingTop || 0, 10);
if (w && h) {
return {
width: w,
height: h
};
}
}
if (element && element.getBoundingClientRect) {
var box = element.getBoundingClientRect();
h = box.bottom - box.top;
w = box.right - box.left;
return {
width: w,
height: h
};
}
return {
width: 0,
height: 0
};
},
childContainingElement: function(parent, element) {
parent = this.unbox(parent);
element = this.unbox(element);
while (element.parentNode != parent) {
if (element == document.body)
return null;
element = element.parentNode;
}
return element;
},
elementBoundingBox: function(element) {
var offset = this.elementOffset(element);
var dimensions = this.elementDimensions(element);
return {
left: offset.left,
top: offset.top,
right: offset.left + dimensions.width - 1,
bottom: offset.top + dimensions.height - 1
};
},
pointWithinElement: function(x, y, element) {
var bb = this.elementBoundingBox(element);
return bb.left <= x && x <= bb.right && bb.top <= y && y <= bb.bottom;
},
elementFromPoint: function(x, y, disregarding, parent) {
disregarding = disregarding || [];
if (!Types.is_array(disregarding))
disregarding = [disregarding];
var backup = [];
for (var i = 0; i < disregarding.length; ++i) {
disregarding[i] = this.unbox(disregarding[i]);
backup.push(disregarding[i].style.zIndex);
disregarding[i].style.zIndex = -1;
}
var element = document.elementFromPoint(x - window.pageXOffset, y - window.pageYOffset);
for (i = 0; i < disregarding.length; ++i)
disregarding[i].style.zIndex = backup[i];
while (element && parent && element.parentNode !== parent)
element = element.parentNode;
return element;
},
elementAddClass: function(element, cls) {
if (!element.className)
element.className = cls;
else if (!this.elementHasClass(element, cls))
element.className = element.className + " " + cls;
},
elementHasClass: function(element, cls) {
return element.className.split(" ").some(function(name) {
return name === cls;
});
},
elementRemoveClass: function(element, cls) {
element.className = element.className.split(" ").filter(function(name) {
return name !== cls;
}).join(" ");
},
elementInsertBefore: function(element, before) {
before.parentNode.insertBefore(element, before);
},
elementInsertAfter: function(element, after) {
if (after.nextSibling)
after.parentNode.insertBefore(element, after.nextSibling);
else
after.parentNode.appendChild(element);
},
elementInsertAt: function(element, parent, index) {
if (index >= parent.children.length)
parent.appendChild(element);
else
parent.insertBefore(element, parent.children[Math.max(0, index)]);
},
elementIndex: function(element) {
var idx = 0;
while (element.previousElementSibling) {
idx++;
element = element.previousElementSibling;
}
return idx;
},
elementPrependChild: function(parent, child) {
if (parent.children.length > 0)
parent.insertBefore(child, parent.firstChild);
else
parent.appendChild(child);
},
// Will find closest parent element, will stop on stopSelector
// example: Dom.elementReplaceClasses(element, '.look-element-class-name', '.stop-on-class-name')
elementFindClosestParent: function(element, selector, stopSelector) {
var _returnVal = null;
while (element) {
if (element.className.indexOf(selector) > -1) {
_returnVal = element;
break;
} else if (stopSelector && element.className.indexOf(stopSelector) > -1) {
break;
}
element = element.parentElement;
}
return _returnVal;
},
// Will replace class names on element
elementReplaceClasses: function(element, replaceClass, replaceWith) {
if (this.elementHasClass(element, replaceClass)) {
this.elementRemoveClass(element, replaceClass);
this.elementAddClass(element, replaceWith);
}
},
// When element in visible port view, will return true
isElementVisible: function(element, fraction) {
fraction = fraction || 0.8;
var offset = this.elementOffset(element);
var x = offset.left;
var y = offset.top;
var w = element.offsetWidth;
var h = element.offsetHeight;
var right = x + w;
var bottom = y + h;
var visibleX = Math.max(0, Math.min(w, window.pageXOffset + window.innerWidth - x, right - window.pageXOffset));
var visibleY = Math.max(0, Math.min(h, window.pageYOffset + window.innerHeight - y, bottom - window.pageYOffset));
var visible = visibleX * visibleY / (w * h);
return (visible > fraction);
},
keyboardUnfocus: function() {
if (document.activeElement)
document.activeElement.blur();
},
passiveEventsSupported: function() {
return Info.isiOS();
},
containerStickyBottom: function(someElement) {
var lastScrollHeight = someElement.scrollHeight;
var critical = false;
var observer = new MutationObserver(function() {
if (critical)
return;
critical = true;
var newScrollHeight = someElement.scrollHeight;
var oldScrollHeight = lastScrollHeight;
lastScrollHeight = newScrollHeight;
if (newScrollHeight > oldScrollHeight)
someElement.scrollTop = someElement.scrollTop + newScrollHeight - oldScrollHeight;
critical = false;
});
observer.observe(someElement, {
childList: true,
subtree: true,
attributes: true,
characterData: true
});
return observer;
},
isInputLikeElement: function(element) {
return element.nodeName === "TEXTAREA" || element.nodeName === "INPUT";
},
copyStringToClipboard: function(s) {
var input = document.createElement("input");
input.style.display = 'none';
document.body.appendChild(input);
input.value = s;
this.copyInputValueToClipboard(input);
document.body.removeChild(input);
},
copyInputValueToClipboard: function(element) {
if (document.body.createTextRange) {
var textRange = document.body.createTextRange();
textRange.moveToElementText(element);
textRange.select();
return textRange.execCommand("Copy");
} else if (window.getSelection && document.createRange) {
var oldContentEditable = element.contentEditable;
var oldReadOnly = element.readOnly;
element.contentEditable = true;
element.readOnly = false;
var range = document.createRange();
range.selectNodeContents(element);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range); // Does not work for Firefox if a textarea or input
if (this.isInputLikeElement(element))
element.select(); // Firefox will only select a form element with select()
if (Info.isiOS())
element.setSelectionRange(0, 999999);
element.contentEditable = oldContentEditable;
element.readOnly = oldReadOnly;
return document.execCommand("copy");
}
return false;
},
onScrollIntoView: function(element, visibilityFraction, callback, context) {
if (this.isElementVisible(element, visibilityFraction)) {
callback.call(context);
return;
}
var cb = function() {
if (Dom.isElementVisible(element, visibilityFraction)) {
callback.call(context);
document.removeEventListener("scroll", cb);
}
};
document.addEventListener("scroll", cb);
}
};
return Dom;
});
Scoped.define("module:Geometry", [], function() {
return {
/**
*
* @param videoWidth
* @param videoHeight
* @param embedWidth
* @param embedHeight
*/
padFitBoxInBox: function(videoWidth, videoHeight, embedWidth, embedHeight) {
var videoAR = videoWidth / videoHeight;
var embedAR = embedWidth / embedHeight;
var scale = videoAR > embedAR ? (embedWidth / videoWidth) : (embedHeight / videoHeight);
return {
scale: scale,
offsetX: videoAR < embedAR ? (embedWidth - videoWidth * scale) / 2 : 0,
offsetY: videoAR > embedAR ? (embedHeight - videoHeight * scale) / 2 : 0
};
}
};
});
Scoped.define("module:Selection", [
"module:Dom"
], function(Dom) {
return {
/** @suppress {checkTypes} */
selectNode: function(node, offset) {
var selection = null;
var range = null;
if (window.getSelection) {
selection = window.getSelection();
selection.removeAllRanges();
range = document.createRange();
} else if (document.selection) {
selection = document.selection;
range = selection.createRange();
}
if (offset) {
range.setStart(node, offset);
range.setEnd(node, offset);
selection.addRange(range);
} else {
range.selectNode(node);
selection.addRange(range);
}
},
/** @suppress {checkTypes} */
selectedHtml: function() {
if (window.getSelection)
return window.getSelection().toString();
else if (document.selection)
return document.selection.createRange().htmlText;
return "";
},
/** @suppress {checkTypes} */
selectionStartOffset: function() {
if (window.getSelection)
return window.getSelection().getRangeAt(0).startOffset;
else if (document.selection)
return document.selection.createRange().startOffset;
return null;
},
/** @suppress {checkTypes} */
selectionEndOffset: function() {
if (window.getSelection)
return window.getSelection().getRangeAt(0).endOffset;
else if (document.selection)
return document.selection.createRange().endOffset;
return null;
},
/** @suppress {checkTypes} */
selectionNonEmpty: function() {
var start = this.selectionStart();
var end = this.selectionEnd();
return start && end && start && end && (start != end || this.selectionStartOffset() != this.selectionEndOffset());
},
/** @suppress {checkTypes} */
selectionContained: function(node) {
return node.contains(this.selectionStart()) && node.contains(this.selectionEnd());
},
/** @suppress {checkTypes} */
selectionNodes: function() {
var result = [];
var start = this.selectionStart();
var end = this.selectionEnd();
result.push(start);
var current = start;
while (current != end) {
current = Dom.traverseNext(current);
result.push(current);
}
return result;
},
/** @suppress {checkTypes} */
selectionLeaves: function() {
return this.selectionNodes().filter(function(node) {
return !node.hasChildNodes();
});
},
/** @suppress {checkTypes} */
selectionStartNode: function() {
if (window.getSelection)
return window.getSelection().getRangeAt(0).startContainer;
else if (document.selection)
return document.selection.createRange().startContainer;
return null;
},
/** @suppress {checkTypes} */
selectionAncestor: function() {
if (window.getSelection)
return window.getSelection().getRangeAt(0).commonAncestorContainer;
else if (document.selection)
return document.selection.createRange().parentElement();
return null;
},
/** @suppress {checkTypes} */
selectionStart: function() {
if (window.getSelection)
return window.getSelection().getRangeAt(0).startContainer;
else if (document.selection)
return document.selection.createRange().startContainer;
return null;
},
/** @suppress {checkTypes} */
selectionEnd: function() {
if (window.getSelection)
return window.getSelection().getRangeAt(0).endContainer;
else if (document.selection)
return document.selection.createRange().endContainer;
return null;
},
/** @suppress {checkTypes} */
selectionSplitOffsets: function() {
var startOffset = this.selectionStartOffset();
var endOffset = this.selectionEndOffset();
var start = this.selectionStart();
var end = this.selectionEnd();
var single = start == end;
if (endOffset < end.wholeText.length) {
end.splitText(endOffset);
if (single)
start = end;
}
if (startOffset > 0) {
start = start.splitText(startOffset);
if (single)
end = start;
}
this.selectRange(start, end);
},
/** @suppress {checkTypes} */
selectRange: function(start_node, end_node, start_offset, end_offset) {
var selection = null;
var range = null;
if (window.getSelection) {
selection = window.getSelection();
selection.removeAllRanges();
range = document.createRange();
} else if (document.selection) {
selection = document.selection;
range = selection.createRange();
}
range.setStart(start_node, start_offset || 0);
range.setEnd(end_node, end_offset || end_node.data.length);
selection.addRange(range);
}
};
});
Scoped.define("module:Upload.ChunkedFileUploader", [
"module:Upload.FileUploader",
"module:Upload.MultiUploader",
"module:Blobs",
"base:Promise",
"base:Objs",
"base:Tokens",
"base:Ajax.Support"
], function(FileUploader, MultiUploader, Blobs, Promise, Objs, Tokens, AjaxSupport, scoped) {
return FileUploader.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(options) {
inherited.constructor.call(this, options);
this._multiUploader = new MultiUploader({
uploadLimit: this._options.uploadLimit
});
this._options.identifierParameter = this._options.identifierParameter || "identifier";
this._options.chunks = Objs.extend({
size: 1000000,
chunkNumberParameter: "chunknumber"
}, this._options.chunks);
this._options.assembly = Objs.extend({
fileNameParameter: "filename",
totalSizeParameter: "totalsize",
chunkNumberParameter: "chunknumber",
fileTypeParameter: "filetype",
ajaxOptions: null
}, this._options.assembly);
},
destroy: function() {
this._multiUploader.destroy();
inherited.destroy.call(this);
},
reset: function() {
inherited.reset.call(this);
this._multiUploader.destroy();
this._multiUploader = new MultiUploader({
uploadLimit: this._options.uploadLimit
});
},
__generateIdentifier: function() {
return Tokens.generate_token();
},
_upload: function() {
var identifier = this.__generateIdentifier();
var file = this._options.isBlob ? this._options.source : this._options.source.files[0];
Blobs.loadFileIntoArrayBuffer(file).success(function(arrayBuffer) {
var chunkNumber = 0;
while (chunkNumber * this._options.chunks.size < file.size) {
var data = {};
data[this._options.chunks.chunkNumberParameter] = chunkNumber + 1;
data[this._options.identifierParameter] = identifier;
var offset = chunkNumber * this._options.chunks.size;
var size = Math.min(this._options.chunks.size, file.size - offset);
this._multiUploader.addUploader(this._multiUploader.auto_destroy(FileUploader.create({
url: this._options.chunks.url || this._options.url,
source: Blobs.createBlobByArrayBufferView(arrayBuffer, offset, size, file.type),
data: Objs.extend(data, this._options.data)
})));
chunkNumber++;
}
this._multiUploader.on("error", function(error) {
this._errorCallback(error);
}, this).on("progress", function(uploaded, total) {
this._progressCallback(uploaded, total);
}, this).on("success", function() {
var data = {};
data[this._options.identifierParameter] = identifier;
data[this._options.assembly.fileNameParameter] = file.name || "blob";
data[this._options.assembly.totalSizeParameter] = file.size;
data[this._options.assembly.chunkNumberParameter] = chunkNumber;
data[this._options.assembly.fileTypeParameter] = file.type;
AjaxSupport.execute(Objs.extend({
method: this._options.method,
uri: this._options.assembly.url || this._options.url,
data: Objs.extend(data, this._options.data)
}, this._options.assembly.ajaxOptions)).success(function() {
this._successCallback();
}, this).error(function(error) {
this._errorCallback(error);
}, this);
}, this);
this._multiUploader.upload();
}, this);
}
};
}, {
supported: function(options) {
return typeof(window.Blob) !== "undefined" && typeof FileReader !== "undefined" && typeof DataView !== "undefined" && options.serverSupportsChunked;
}
});
});
Scoped.define("module:Upload.CordovaFileUploader", [
"module:Upload.FileUploader",
"module:Info",
"base:Promise",
"base:Ajax.Support",
"base:Objs"
], function(FileUploader, Info, Promise, AjaxSupport, Objs, scoped) {
return FileUploader.extend({
scoped: scoped
}, {
_acquirePermission: function() {
var promise = Promise.create();
if (Info.isAndroid()) {
var perms = cordova.plugins.permissions;
perms.checkPermission(perms.READ_EXTERNAL_STORAGE, function(status) {
if (!status.hasPermission) {
perms.requestPermission(perms.READ_EXTERNAL_STORAGE, function(status) {
if (status.hasPermission)
promise.asyncSuccess(true);
else
promise.asyncError("Access to storage was not granted");
}, function() {
promise.asyncError("Could not request storage permission");
});
} else
promise.asyncSuccess(true);
}, function() {
promise.asyncError("Could not check storage permission");
});
} else
promise.asyncSuccess(true);
return promise;
},
_upload: function() {
return this._acquirePermission().mapSuccess(function() {
return window.resolveLocalFileSystemURL ? this._uploadWithLocalFileSystem() : this._uploadWithPlugin();
}, this);
},
_uploadWithPlugin: function() {
var self = this;
//var fileURI = this._options.source.localURL;
var fileURI = this._options.source.fullPath.split(':')[1];
var fileUploadOptions = new window.FileUploadOptions();
fileUploadOptions.fileKey = "file";
fileUploadOptions.fileName = fileURI.substr(fileURI.lastIndexOf('/') + 1);
fileUploadOptions.mimeType = this._options.source.type;
fileUploadOptions.httpMethod = this._options.method;
fileUploadOptions.params = this._options.data;
var fileTransfer = new window.FileTransfer();
fileTransfer.upload(fileURI, this._options.url, function(data) {
self._successCallback(data);
}, function(data) {
self._errorCallback(data);
}, fileUploadOptions);
},
_uploadWithLocalFileSystem: function() {
var self = this;
var fileURI = this._options.source.fullPath;
window.resolveLocalFileSystemURL(fileURI, function(fileEntry) {
fileEntry.file(function(file) {
var reader = new FileReader();
reader.onloadend = function() {
// Create a blob based on the FileReader "result", which we asked to be retrieved as an ArrayBuffer
var blob = new Blob([new Uint8Array(this.result)], {
type: self._options.source.type
});
var data = Objs.clone(self._options.data || {}, 1);
data.file = blob;
return AjaxSupport.execute({
method: self._options.method,
uri: self._options.url,
decodeType: "text",
data: data
}, self._progressCallback, self).success(self._successCallback, self).error(self._errorCallback, self);
};
// Read the file as an ArrayBuffer
reader.readAsArrayBuffer(file);
}, function(err) {
self._errorCallback(err);
});
}, function(err) {
self._errorCallback(err);
});
}
}, {
supported: function(options) {
var result = !!navigator.device &&
!!navigator.device.capture &&
!!navigator.device.capture.captureVideo &&
!!(window.resolveLocalFileSystemURL || (window.FileTransfer && window.FileUploadOptions)) &&
!options.isBlob &&
("localURL" in options.source || "fullPath" in options.source);
return result;
}
});
});
Scoped.define("module:Upload.FileUploader", [
"base:Classes.ConditionalInstance",
"base:Events.EventsMixin",
"base:Objs",
"base:Types",
"base:Async",
"base:Promise"
], function(ConditionalInstance, EventsMixin, Objs, Types, Async, Promise, scoped) {
return ConditionalInstance.extend({
scoped: scoped
}, [EventsMixin, function(inherited) {
return {
constructor: function(options) {
inherited.constructor.call(this, options);
// idle, uploading, success, error
this._uploaded = 0;
this._state = "idle";
},
_setState: function(state, triggerdata, xmlhttprequest) {
this._state = state;
this.trigger(state, triggerdata, xmlhttprequest);
this.trigger("state", state, triggerdata, xmlhttprequest);
},
state: function() {
return this._state;
},
data: function() {
return this._data;
},
progress: function() {
return {
uploaded: this._uploaded,
total: this._total
};
},
reset: function() {
if (this.state() === "error") {
this._setState("idle");
delete this._data;
this._uploaded = 0;
delete this._total;
delete this._request;
}
},
upload: function() {
if (this.state() !== "idle")
return this;
this._setState("uploading");
this.__upload();
return this;
},
__upload: function() {
this._upload();
this._options.resilience--;
},
_upload: function() {},
_progressCallback: function(uploaded, total) {
if (this.state() !== "uploading")
return;
this._uploaded = uploaded;
this._total = total;
this.trigger("progress", uploaded, total);
},
_successCallback: function(data) {
if (this.state() !== "uploading")
return;
this._data = data;
this._setState("success", data, this._request);
},
_errorCallback: function(data) {
if (this.state() !== "uploading")
return;
try {
if (data.data)
data = data.data();
if (Types.is_string(data))
data = JSON.parse(data);
} catch (e) {}
if (this._options.resilience > 0) {
if (!this._options.resilienceCheck || this._options.resilienceCheck(data)) {
Async.eventually(function() {
this.__upload();
}, this, this._options.resilience_delay);
return;
}
}
if (!this._options.essential) {
this._successCallback({});
return;
}
this._data = data;
this._setState("error", data, this._request);
},
uploadedBytes: function() {
return this._uploaded;
},
totalBytes: function() {
if (this._total)
return this._total;
if (this._options.source) {
if (!this._options.isBlob && this._options.source.files && this._options.source.files[0])
return this._options.source.files[0].size;
return this._options.source.size || 0;
}
return 0;
}
};
}], {
_initializeOptions: function(options) {
options = options || {};
return Objs.extend({
//url: "",
//source: null,
method: "POST",
serverSupportChunked: false,
serverSupportPostMessage: false,
isBlob: typeof(window.Blob) !== "undefined" && options.source instanceof(window.Blob),
resilience: 1,
resilience_delay: 1000,
s3: false,
essential: true,
data: {}
}, options);
}
});
});
Scoped.define("module:Upload.CustomUploader", [
"module:Upload.FileUploader"
], function(FileUploader, scoped) {
return FileUploader.extend({
scoped: scoped
}, {
_upload: function() {
this.trigger("upload", this._options);
},
progressCallback: function(uploaded, total) {
this._progressCallback(uploaded, total);
},
successCallback: function(data) {
this._successCallback(data);
},
errorCallback: function(data) {
this._errorCallback(data);
}
});
});
Scoped.extend("module:Upload.FileUploader", [
"module:Upload.FileUploader",
"module:Upload.FormDataFileUploader",
"module:Upload.FormIframeFileUploader",
"module:Upload.CordovaFileUploader",
"module:Upload.ChunkedFileUploader",
"module:Upload.S3MultipartFileUploader"
], function(FileUploader, FormDataFileUploader, FormIframeFileUploader, CordovaFileUploader, ChunkedFileUploader, S3MultipartFileUploader) {
FileUploader.register(S3MultipartFileUploader, 5);
FileUploader.register(ChunkedFileUploader, 4);
FileUploader.register(CordovaFileUploader, 3);
FileUploader.register(FormDataFileUploader, 2);
FileUploader.register(FormIframeFileUploader, 1);
return {};
});
Scoped.define("module:Upload.FormDataFileUploader", [
"module:Upload.FileUploader",
"module:Info",
"base:Ajax.Support",
"base:Objs",
"base:Types"
], function(FileUploader, Info, AjaxSupport, Objs, Types, scoped) {
return FileUploader.extend({
scoped: scoped
}, {
_upload: function() {
var data = Objs.clone(Types.is_empty(this._options.data) ? {} : this._options.data, 1);
data.file = this._options.isBlob ? this._options.source : this._options.source.files[0];
this._request = AjaxSupport.create();
return AjaxSupport.execute({
method: this._options.method,
uri: this._options.url,
decodeType: "text",
data: data,
noFormData: this._options.noFormData
}, this._progressCallback, this, this._request).success(this._successCallback, this).error(this._errorCallback, this);
}
}, {
supported: function(options) {
if (Info.isInternetExplorer() && Info.internetExplorerVersion() <= 9 || (Info.isCordova() && ("localURL" in options.source || "fullPath" in options.source)))
return false;
try {
new(window.FormData)();
} catch (e) {
return false;
}
return true;
}
});
});
Scoped.define("module:Upload.FormIframeFileUploader", [
"module:Upload.FileUploader",
"base:Net.Uri",
"base:Objs",
"base:Async"
], function(FileUploader, Uri, Objs, Async, scoped) {
return FileUploader.extend({
scoped: scoped
}, {
_upload: function() {
var self = this;
var iframe = document.createElement("iframe");
var id = "upload-iframe-" + this.cid();
iframe.id = id;
iframe.name = id;
iframe.style.display = "none";
var form = document.createElement("form");
form.method = this._options.method;
form.target = id;
form.style.display = "none";
document.body.appendChild(iframe);
document.body.appendChild(form);
var oldParent = this._options.source.parent;
form.appendChild(this._options.source);
if (!this._options.source.name)
this._options.source.name = "file";
Objs.iter(this._options.data, function(value, key) {
var input = document.createElement("input");
input.type = "hidden";
input.name = key;
input.value = value;
form.appendChild(input);
}, this);
var post_message_fallback = !("postMessage" in window);
var handle_success = null;
var message_event_handler = function(event) {
handle_success(event.data);
};
iframe.onerror = function() {
if (post_message_fallback)
window.postMessage = null;
window.removeEventListener("message", message_event_handler, false);
if (oldParent)
oldParent.appendChild(self._options.source);
document.body.removeChild(form);
document.body.removeChild(iframe);
self._errorCallback();
};
var post_message_key = this._options.serverPostMessageKey || "_postmessage";
var post_message_id_key = this._options.serverPostMessageIdKey || "_postmessageid";
var post_message_id_value = this.cid();
var support_id = this._options.serverSupportPostMessageId;
var query_params = {};
query_params[post_message_key] = true;
if (support_id)
query_params[post_message_id_key] = post_message_id_value;
form.action = Uri.appendUriParams(this._options.url, query_params);
form.encoding = form.enctype = "multipart/form-data";
handle_success = function(raw_data) {
try {
var data = JSON.parse(raw_data);
if (support_id && data[post_message_id_key] !== post_message_id_value)
return;
if (post_message_fallback)
window.postMessage = null;
if (oldParent)
oldParent.appendChild(self._options.source);
document.body.removeChild(form);
document.body.removeChild(iframe);
self._successCallback(data);
Async.eventually(function() {
window.removeEventListener("message", message_event_handler, false);
});
} catch (e) {}
};
window.addEventListener("message", message_event_handler, false);
if (post_message_fallback)
window.postMessage = handle_success;
form.submit();
}
}, {
supported: function(options) {
return !options.isBlob && options.serverSupportPostMessage;
}
});
});
Scoped.define("module:Upload.MultiUploader", [
"module:Upload.FileUploader",
"base:Objs"
], function(FileUploader, Objs, scoped) {
return FileUploader.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(options) {
inherited.constructor.call(this, options);
this._uploaders = {};
this._uploadLimit = this._options.uploadLimit || 5;
this._uploadingCount = 0;
this._end = !this._options.manualEnd;
},
end: function() {
this._end = true;
this._updateState();
},
addUploader: function(uploader) {
this._uploaders[uploader.cid()] = uploader;
uploader.on("state", this._updateState, this);
uploader.on("progress", this._updateProgress, this);
if (this.state() === "uploading") {
if (uploader.state() === "error")
uploader.reset();
if (uploader.state() === "idle" && this._uploadingCount < this._uploadLimit) {
this._uploadingCount++;
uploader.upload();
}
}
return this;
},
_upload: function() {
Objs.iter(this._uploaders, function(uploader) {
if (uploader.state() === "error")
uploader.reset();
if (uploader.state() === "idle" && this._uploadingCount < this._uploadLimit) {
this._uploadingCount++;
uploader.upload();
}
}, this);
this._updateState();
},
_updateState: function() {
if (this.state() !== "uploading")
return;
this._uploadingCount = 0;
var error = false;
var idleCount = 0;
Objs.iter(this._uploaders, function(uploader) {
switch (uploader.state()) {
case "uploading":
this._uploadingCount++;
break;
case "error":
error = true;
break;
case "idle":
idleCount++;
break;
}
}, this);
if (idleCount > 0 && this._uploadingCount < this._uploadLimit) {
Objs.iter(this._uploaders, function(uploader) {
if (this._uploadingCount < this._uploadLimit && uploader.state() === "idle") {
uploader.upload();
idleCount--;
this._uploadingCount++;
}
}, this);
}
if (this._uploadingCount > 0)
return;
if (!this._end)
return;
var datas = [];
Objs.iter(this._uploaders, function(uploader) {
var result = (error && uploader.state() === "error") || (!error && uploader.state() === "success") ? uploader.data() : undefined;
datas.push(result);
}, this);
if (error > 0)
this._errorCallback(datas);
else
this._successCallback(datas);
},
_updateProgress: function() {
if (this.state() !== "uploading")
return;
this._progressCallback(this.uploadedBytes(), this.totalBytes());
},
uploadedBytes: function() {
var uploaded = 0;
Objs.iter(this._uploaders, function(uploader) {
uploaded += uploader.uploadedBytes();
}, this);
return uploaded;
},
totalBytes: function() {
var total = 0;
Objs.iter(this._uploaders, function(uploader) {
total += uploader.totalBytes();
}, this);
return total;
}
};
});
});
Scoped.define("module:Upload.S3MultipartFileUploader", [
"module:Upload.FileUploader",
"module:Upload.MultiUploader",
"module:Upload.S3MultipartSupport"
], function(FileUploader, MultiUploader, S3Support, scoped) {
return FileUploader.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(options) {
inherited.constructor.call(this, options);
this._parts = [];
this._multiUploader = new MultiUploader({
uploadLimit: this._options.uploadLimit
});
},
destroy: function() {
this._multiUploader.destroy();
inherited.destroy.call(this);
},
reset: function() {
inherited.reset.call(this);
this._parts = [];
this._multiUploader.destroy();
this._multiUploader = new MultiUploader({
uploadLimit: this._options.uploadLimit
});
},
getUploadId: function() {
return this._options.uploadId;
},
_upload: function() {
var file = this._options.isBlob ? this._options.source : this._options.source.files[0];
if (!S3Support.validateFileSize(file.size)) {
this._setState("error", "File size is too big");
return;
}
var chunks = S3Support.chunkFile(file, {
numberOfChunks: this._options.parts,
chunkSize: this._options.chunkSize
});
var maximumNumberOfParts = 0;
for (var p in this._options.urls) {
if (this._options.urls.hasOwnProperty(p)) {
maximumNumberOfParts++;
}
}
if (chunks.length > maximumNumberOfParts) {
this._setState("error", "Not enough parts to upload file");
return;
}
chunks.forEach(function(chunk, index) {
var partNumber = index + 1;
var chunkUploader = this._multiUploader.auto_destroy(FileUploader.create({
method: "PUT",
url: this._options.urls[partNumber],
source: chunk,
resilience: this._options.resilience,
noFormData: true
}));
this._multiUploader.addUploader(chunkUploader);
chunkUploader.on("success", function(_, request) {
this._parts.push({
ETag: request.getResponseHeader("ETag"),
PartNumber: partNumber
});
}, this);
}.bind(this));
this._multiUploader
.on("error", this._errorCallback, this)
.on("progress", this._progressCallback, this)
.on("success", function() {
this._successCallback(S3Support.sortParts(this._parts));
}, this);
this._multiUploader.upload();
}
};
}, {
supported: function(options) {
return options.s3 && options.urls;
}
});
});
Scoped.define("module:Upload.S3MultipartSupport", [
"base:Maths"
], function(Maths) {
var MIN_CHUNK_SIZE = 5 * 1024 * 1024; // 5 MB
var MAX_CHUNK_SIZE = 5 * 1024 * 1024 * 1024; // 5 GB
var MAX_FILE_SIZE = 5 * 1024 * 1024 * 1024 * 1024; // 5 TB
var MAX_NUMBER_OF_CHUNKS = 10000;
var DEFAULT_CHUNK_SIZE = 50 * 1024 * 1024; // 50 MB
var MAX_FILE_SIZE_FOR_DEFAULT_CHUNK_SIZE = DEFAULT_CHUNK_SIZE * MAX_NUMBER_OF_CHUNKS;
return {
/**
* Split file into multiple chunks that respect AWS limits for multipart upload.
* @param {Blob | File} file
* @param {Object} [options]
* @param {number} options.numberOfChunks
* @param {number} options.chunkSize
* @returns {Blob[]} array of chunks
*/
chunkFile: function(file, options) {
if (!this.validateFileSize(file.size)) {
return [];
}
var chunkSize = file.size > MAX_FILE_SIZE_FOR_DEFAULT_CHUNK_SIZE ? MAX_CHUNK_SIZE : DEFAULT_CHUNK_SIZE;
if (options.chunkSize) {
chunkSize = this.clampChunkSize(options.chunkSize);
var numberOfChunks = this.calculateNumberOfChunks(file.size, chunkSize);
if (options.numberOfChunks && numberOfChunks > this.clampNumberOfChunks(options.numberOfChunks)) {
// chunk size is too small for the current number of chunks, we are prioritizing number of chunks over chunk size
chunkSize = this.clampChunkSize(this.calculateChunkSize(file.size, this.clampNumberOfChunks(options.numberOfChunks)));
}
} else if (options.numberOfChunks) {
chunkSize = this.clampChunkSize(this.calculateChunkSize(file.size, this.clampNumberOfChunks(options.numberOfChunks)));
}
var startPointer = 0;
var endPointer = file.size;
var chunks = [];
while (startPointer < endPointer) {
chunks.push(file.slice(startPointer, startPointer + chunkSize));
startPointer = startPointer + chunkSize;
}
return chunks;
},
/**
* Sort parts by part number, as AWS require them to be sent in order.
* @param {Object[]} parts - list of parts
* @param {string} parts[].ETag - the part's ETag response header, which is sent after successfull part upload
* @param {number} parts[].PartNumber - the part's part number
* @returns {Object[]}
*/
sortParts: function(parts) {
return parts.sort(function(prevPart, nextPart) {
return prevPart.PartNumber < nextPart.PartNumber ? -1 : 1;
});
},
/**
* Validate whether file size is within AWS limits for multipart upload.
* @param {number} fileSize - file size in bytes
* @returns {boolean}
*/
validateFileSize: function(fileSize) {
return fileSize > 0 && fileSize <= MAX_FILE_SIZE;
},
/**
* Validate whether chunk size is within AWS limits for multipart upload.
* @param {number} chunkSize - chunk size in bytes
* @returns {boolean}
*/
validateChunkSize: function(chunkSize) {
return chunkSize >= MIN_CHUNK_SIZE && chunkSize <= MAX_CHUNK_SIZE;
},
/**
* Validate whether number of chunks is within AWS limits for multipart upload.
* @param {number} numberOfChunks
* @returns {boolean}
*/
validateNumberOfChunks: function(numberOfChunks) {
return numberOfChunks >= 1 && numberOfChunks <= MAX_NUMBER_OF_CHUNKS;
},
/**
* Calculate chunk size, given the file size and number of chunks.
* @param {number} fileSize - file size in bytes
* @param {number} numberOfChunks
* @returns {number} chunk size in bytes
*/
calculateChunkSize: function(fileSize, numberOfChunks) {
return Math.ceil(fileSize / numberOfChunks);
},
/**
* Calculate number of chunks, given the file size and chunk size.
* @param {number} fileSize - file size in bytes
* @param {number} chunkSize - chunk size in bytes
* @returns {number}
*/
calculateNumberOfChunks: function(fileSize, chunkSize) {
return Math.ceil(fileSize / (chunkSize || DEFAULT_CHUNK_SIZE));
},
/**
* Clamp chunk size using AWS limits as upper and lower bounds.
* @param {number} chunkSize
* @returns {number} clamped chunk size
*/
clampChunkSize: function(chunkSize) {
return Maths.clamp(chunkSize, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE);
},
/**
* Clamp number of chunks using AWS limits as upper and lower bounds.
* @param {number} numberOfChunks
* @returns {number} clamped number of chunks
*/
clampNumberOfChunks: function(numberOfChunks) {
return Maths.clamp(numberOfChunks, 1, MAX_NUMBER_OF_CHUNKS);
}
};
});
Scoped.define("module:Upload.StreamingFileUploader", [
"module:Upload.FileUploader",
"module:Upload.MultiUploader",
"base:Promise",
"base:Objs",
"base:Tokens",
"base:Ajax.Support"
], function(FileUploader, MultiUploader, Promise, Objs, Tokens, AjaxSupport, scoped) {
return FileUploader.extend({
scoped: scoped
}, function(inherited) {
return {
constructor: function(options) {
inherited.constructor.call(this, options);
this._multiUploader = new MultiUploader({
uploadLimit: this._options.uploadLimit,
manualEnd: true
});
this._options.identifierParameter = this._options.identifierParameter || "identifier";
this._options.chunks = Objs.extend({
chunkNumberParameter: "chunknumber"
}, this._options.chunks);
this._options.assembly = Objs.extend({
totalSizeParameter: "totalsize",
chunkNumberParameter: "chunknumber",
ajaxOptions: null
}, this._options.assembly);
this._identifier = this.__generateIdentifier();
this._chunkNumber = 0;
this._totalSize = 0;
},
destroy: function() {
this._multiUploader.destroy();
inherited.destroy.call(this);
},
reset: function() {
inherited.reset.call(this);
this._multiUploader.destroy();
this._multiUploader = new MultiUploader({
uploadLimit: this._options.uploadLimit
});
this._identifier = this.__generateIdentifier();
this._chunkNumber = 0;
this._totalSize = 0;
},
__generateIdentifier: function() {
return Tokens.generate_token();
},
addChunk: function(blob) {
var data = {};
data[this._options.chunks.chunkNumberParameter] = this._chunkNumber + 1;
data[this._options.identifierParameter] = this._identifier;
this._multiUploader.addUploader(this._multiUploader.auto_destroy(FileUploader.create({
url: this._options.chunks.url || this._options.url,
source: blob,
data: Objs.extend(data, this._options.data)
})));
this._chunkNumber++;
this._totalSize += blob.size;
},
end: function() {
this._multiUploader.end();
},
_upload: function() {
this._multiUploader.on("error", function(error) {
this._errorCallback(error);
}, this).on("progress", function(uploaded, total) {
this._progressCallback(uploaded, total);
}, this).on("success", function() {
var data = {};
data[this._options.identifierParameter] = this._identifier;
data[this._options.assembly.totalSizeParameter] = this._totalSize;
data[this._options.assembly.chunkNumberParameter] = this._chunkNumber;
AjaxSupport.execute(Objs.extend({
method: this._options.method,
uri: this._options.assembly.url || this._options.url,
data: Objs.extend(data, this._options.data)
}, this._options.assembly.ajaxOptions)).success(function() {
this._successCallback();
}, this).error(function(error) {
this._errorCallback(error);
}, this);
}, this);
this._multiUploader.upload();
}
};
}, {
supported: function(options) {
return typeof(window.Blob) !== "undefined" && options.serverSupportsChunked;
}
});
});
}).call(Scoped);