fit-ui
Version:
Object Oriented framework for building rich User Interfaces
1,265 lines (1,092 loc) • 1.31 MB
JavaScript
(function (factory)
{
// UMD module (AMD, CommonJS, and Global).
// Good documentation on modules:
// http://2ality.com/2015/12/babel-commonjs.html
// http://davidbcalhoun.com/2014/what-is-amd-commonjs-and-umd
// CommonJS module.
// Enables support for bundlers such as WebPack.
if (typeof module === "object" && typeof module.exports === "object")
{
// Register module the same way Babel does:
// http://2ality.com/2015/12/babel-commonjs.html#how-babel-compiles-es6-modules-to-commonjs
var fit = factory();
// Export as one large module (Fit)
// module.exports.Fit = fit;
// Export each namespace of Fit.UI as individual modules (Array, Core, Dom, etc.)
// This way we can do this: import { Controls as c, Dom as d } from "fit-ui"
module.exports = fit;
// module.exports.default = fit; // DISABLED, breaks IE8
Object.defineProperty(module.exports, "__esModule", { value: true });
}
// AMD module for runtime loading.
// Usage: require(["libs/Fit.UI/Fit.UI.js"], function(fit) { console.log("Loaded", fit); });
// Make sure to load RequireJS first - demo: https://codepen.io/anon/pen/KvMGNa?editors=0010
else if (typeof define === "function" && define.amd)
{
define(["require", "exports"], factory);
}
// Global instance
else if (window)
{
var src = document.scripts[document.scripts.length-1].src;
var paramSeparatorPos = src.indexOf("?");
if (paramSeparatorPos > -1)
{
// Argument(s) found in src reference - add instance to custom global variable name.
// First argument is expected to be the name of the Fit instance.
var name = src.substring(paramSeparatorPos + 1, (src.indexOf("&") === -1 ? src.length : src.indexOf("&")));
if (name.indexOf("=") === -1) // Make sure name was defined in the form of e.g. ?Fit and not ?arg=val
{
window[name] = factory();
}
else // Name was not valid, it was in the form of ?arg=val
{
window.Fit = factory();
}
}
else
{
window.Fit = factory();
}
}
})(function()
{
//"use strict"; // Not supported by IE9 and below - may cause different runtime behaviour - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
var fitInstance =
(function(){
;(function() // Terminate script if browser is not capable of running Fit.UI
{
if (!window.JSON || !window.NodeList) // JSON and NodeList are not available on IE7 and older
{
if (navigator.userAgent.indexOf("MSIE") > -1)
throw new Error("Browser not supported - Internet Explorer 8 or newer is required - make sure Compatibility View is not enabled");
else
throw new Error("Browser not supported");
}
return true;
})();
;(function() // Prevent Legacy IE from choking if e.g. console.log(..) is called without developer tools open
{
if (!window.console)
window.console = {};
var shims = [ "log", "debug", "info", "error", "warn", "trace" ];
for (var i = 0 ; i < shims.length ; i++)
{
if (!console[shims[i]])
console[shims[i]] = function() {};
}
})();
/// <container name="Fit.Core">
/// Core features extending the capabilities of native JS
/// </container>
var Fit = {};
Fit.Core = {};
/// <function container="Fit.Core" name="Extend" access="public" static="true" returns="object">
/// <description>
/// Extend any object with the public members of a super class.
///
/// MyClass = function(controlId)
/// {
///      Fit.Core.Extend(this, MySuperClass).Apply();
/// }
///
/// The code above defines a class called MyClass which extends from MySuperClass.
/// Use Apply() to pass variables to the super class constructor as shown below:
///
/// Male = function(name, age)
/// {
///      Fit.Core.Extend(this, Person).Apply("Male", name, age);
/// }
///
/// Notice that calling just Extend(..) without calling Apply() on the object returned,
/// will not cause extension to occure. Apply() must be called, with or without parameters.
///
/// Notice that Fit.UI supports multiple inheritance. Be careful not to extend from multiple
/// classes implementing functions with identical names, or at least be aware that the last
/// class from which the derivative extends, takes precedence.
/// </description>
/// <param name="subInstance" type="object"> Instance of sub class to extend </param>
/// <param name="superType" type="function"> Class (function) to extend from </param>
/// </function>
Fit.Core.Extend = function(subInstance, superType)
{
Fit.Validation.ExpectIsSet(subInstance);
Fit.Validation.ExpectFunction(superType);
// Notice that we support multiple inheritance. For that reason we
// cannot do something like the code below to support instanceof:
// Human.prototype = Object.create(Creature.prototype);
// Human.prototype.constructor = Creature;
// As an alternative to instanceof, use Fit.Core.InstanceOf(..).
var binder =
{
Apply: function()
{
superType.apply(subInstance, arguments);
// Support for Fit.Core.Extends(..)
if (!subInstance._internal)
subInstance._internal = {};
if (!subInstance._internal.Extends)
subInstance._internal.Extends = [];
Fit.Array.Add(subInstance._internal.Extends, superType);
}
}
return binder;
}
/// <function container="Fit.Core" name="Extends" access="public" static="true" returns="boolean">
/// <description>
/// Returns boolean indicating whether given object is an extension of a given super type - see Fit.Core.Extend(..).
/// Also look into Fit.Core.InstanceOf(..) which may provide the desired behaviour.
/// </description>
/// <param name="instance" type="object"> Object instance </param>
/// <param name="superType" type="function"> Reference to super class (function) </param>
/// </function>
Fit.Core.Extends = function(instance, superType)
{
Fit.Validation.ExpectIsSet(instance);
Fit.Validation.ExpectFunction(superType);
return (instance._internal !== undefined && instance._internal.Extends !== undefined && Fit.Array.Contains(instance._internal.Extends, superType) === true);
}
/// <function container="Fit.Core" name="InstanceOf" access="public" static="true" returns="boolean">
/// <description>
/// Returns boolean indicating whether given object is an instance or extension of a given class type - see Fit.Core.Extend(..).
/// This is equivalent of: var result = (obj instanceof MyType || Fit.Core.Extends(obj, MyType));
/// </description>
/// <param name="instance" type="object"> Object instance </param>
/// <param name="type" type="function"> Reference to class (function) </param>
/// </function>
Fit.Core.InstanceOf = function(instance, type)
{
return (instance instanceof type || Fit.Core.Extends(instance, type) === true);
}
/// <function container="Fit.Core" name="CreateOverride" access="public" static="true" returns="function">
/// <description>
/// Create a function override for any given function using the approach below.
///
/// this.SayHello = function(name) { alert("Hello " + name); }
/// this.SayHello = Fit.Core.CreateOverride(this.SayHello, function(name)
/// {
/// console.log(name + " logged in");
/// console.log(name + " is using the following browser: " + navigator.userAgent);
///
/// base(name); // Call original SayHello function
/// });
///
/// Notice how base(..) allows us to call the original function.
/// </description>
/// <param name="originalFunction" type="function"> Reference to function to override </param>
/// <param name="newFunction" type="function"> Reference to replacement function </param>
/// </function>
Fit.Core.CreateOverride = function(originalFunction, newFunction)
{
Fit.Validation.ExpectFunction(originalFunction);
Fit.Validation.ExpectFunction(newFunction);
return function()
{
var orgBase = window.base; // May already exist
window.base = originalFunction; // Globally accessible base function
var error = null;
var result = undefined;
try // Make sure we can clean up globally accessible base function in case of errors
{
// The arguments variable is actually not an ordinary array
// (see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/arguments).
// Browsers implementing ECMAScript prior to version 5 (e.g. IE8) require apply(..) to be
// called with an ordinary array containing the arguments.
var args = [];
for (var i = 0 ; i < arguments.length ; i++)
args[i] = arguments[i];
if (args.length > 0)
result = newFunction.apply(this, args);
else
result = newFunction.apply(this);
}
catch (err)
{
error = err;
}
if (orgBase)
{
window.base = orgBase;
}
else
{
try
{
delete window.base; // Fails in IE8 with "Object doesn't support this action"
}
catch (err)
{
window.base = undefined;
}
}
if (error !== null)
Fit.Validation.ThrowError(error);
if (result !== undefined)
return result;
}
}
/// <function container="Fit.Core" name="CreateDebouncer" access="public" static="true" returns="Fit.CoreTypeDefs.DebounceFunction">
/// <description>
/// Create a debouncing function that delays execution the specified number of milliseconds.
/// Invoking function multiple times merely postpone execution the specified number of milliseconds.
/// This can greatly increase performance for expensive operations invoked often.
/// </description>
/// <param name="func" type="function"> Reference to function to invoke </param>
/// <param name="timeout" type="integer"> Number of milliseconds to postpone execution </param>
/// <param name="thisArg" type="any" default="undefined"> The value 'this' resolves to within debounced function </param>
/// </function>
Fit.Core.CreateDebouncer = function(func, timeoutMilliseconds, thisArg)
{
var timeout = -1;
var lastArgs = [];
/// <container name="Fit.CoreTypeDefs.DebounceFunction">
/// </container>
var d =
{
/// <function container="Fit.CoreTypeDefs.DebounceFunction" name="Cancel" access="public">
/// <description> Cancel debounced function if scheduled for execution </description>
/// </function>
Cancel: function()
{
if (timeout !== -1)
{
clearTimeout(timeout);
lastArgs = [];
timeout = -1;
}
},
/// <member container="Fit.CoreTypeDefs.DebounceFunction" name="Invoke" type="function">
/// <description> Schedule or re-schedule execution of function </description>
/// </member>
Invoke: function() // Defined as a <member> of type function to allow variable arguments which <function> does not allow
{
if (timeout !== -1)
{
clearTimeout(timeout);
}
lastArgs = arguments;
timeout = setTimeout(function()
{
var args = lastArgs;
lastArgs = [];
timeout = -1;
func.apply(thisArg || this, args); // Might call Invoke again, so do not do cleanup below this line!
}, timeoutMilliseconds);
},
/// <function container="Fit.CoreTypeDefs.DebounceFunction" name="Flush" access="public">
/// <description> Force execution of debounced function if scheduled for execution </description>
/// </function>
Flush: function()
{
if (timeout !== -1)
{
var args = lastArgs; // Cancel() clears lastArgs
d.Cancel();
func.apply(thisArg || this, args);
}
}
};
return d;
}
/// <function container="Fit.Core" name="IsEqual" access="public" static="true" returns="boolean">
/// <description>
/// Compare two JavaScript objects to determine whether they are identical.
/// Returns True if objects are identical (equal), otherwise False.
/// Functions are compared by reference, not by value.
/// Custom properties set on native JS objects (e.g. Array.XYZ) are not compared, only
/// values are. Naturally JSON objects will be fully compared, including all properties.
/// Be aware of self referencing variables and circular structures, which
/// will cause an infinite loop, and eventually a stack overflow exception.
/// DOM objects and window/frame instances are not supported.
/// </description>
/// <param name="jsObj1" type="object"> JS object to compare agains second JS object </param>
/// <param name="jsObj2" type="object"> JS object to compare agains first JS object </param>
/// </function>
Fit.Core.IsEqual = function(jsObj1, jsObj2)
{
// TEST CASE: Example below is supposed to return: TRUE!
/*var f1 = function() { alert("Hello"); }
var f2 = f1;
Fit.Core.IsEqual(
{
str: "Hello world",
num: 123,
dec: 123.321,
date: new Date("2014-12-01 13:02:23"),
bool: true,
bool2: false,
arr: [100, 200, 250, 400],
arr2: ["Hello", "world"],
arr3: [123, "hello", true, false, new Date("1990-01-20"), [1,2,3], { x: { "hapsen": f1, "hello": new Array(1,2,3) } }],
obj: { a: 123, b: 123.321, c: true, d: false, e: new Date("1993-06-25"), f: "hello", g: null, h: undefined }
},
{
str: "Hello world",
num: 123,
dec: 123.321,
date: new Date("2014-12-01 13:02:23"),
bool: true,
bool2: false,
arr: [100, 200, 250, 400],
arr2: ["Hello", "world"],
arr3: [123, "hello", true, false, new Date("1990-01-20"), [1,2,3], { x: { "hapsen": f2, "hello": new Array(1,2,3) } }],
obj: { a: 123, b: 123.321, c: true, d: false, e: new Date("1993-06-25"), f: "hello", g: null, h: undefined }
});*/
if (typeof(jsObj1) !== typeof(jsObj2))
return false;
if ((jsObj1 === undefined && jsObj2 === undefined) || (jsObj1 === null && jsObj2 === null))
{
return true;
}
else if (typeof(jsObj1) === "string" || typeof(jsObj1) === "boolean")
{
return (jsObj1 === jsObj2);
}
else if (typeof(jsObj1) === "number")
{
if (isNaN(jsObj1) === true && isNaN(jsObj2) === true) // NaN variables are not comparable!
return true;
else
return (jsObj1 === jsObj2);
}
else if (jsObj1 instanceof Date && jsObj2 instanceof Date)
{
return (jsObj1.getTime() === jsObj2.getTime());
}
else if (jsObj1 instanceof Array && jsObj2 instanceof Array)
{
if (jsObj1.length !== jsObj2.length)
return false;
for (var i = 0 ; i < jsObj1.length ; i++)
{
if (Fit.Core.IsEqual(jsObj1[i], jsObj2[i]) === false)
return false;
}
return true;
}
else if (typeof(jsObj1) === "object" && typeof(jsObj2) === "object" && jsObj1 !== null && jsObj2 !== null) // typeof(null) returns "object"
{
var identical = true;
var keys = Fit.Array.Merge(Fit.Array.GetKeys(jsObj1), Fit.Array.GetKeys(jsObj2));
Fit.Array.ForEach(keys, function(k)
{
if (Fit.Core.IsEqual(jsObj1[k], jsObj2[k]) === false)
{
identical = false;
return false;
}
});
return identical;
}
else if (typeof(jsObj1) === "function" && typeof(jsObj2) === "function")
{
// Returns True in the following situation:
// var f1 = function() { alert("Hello"); }
// var f2 = f1;
// Fit.Core.IsEqual(f1, f2);
// Returns False in the following situation:
// var f1 = function() { alert("Hello"); }
// var f2 = function() { alert("Hello"); }
// Fit.Core.IsEqual(f1, f2);
return (jsObj1 === jsObj2);
}
return false;
}
/// <container name="Fit.Core.MergeOverwriteBehaviour">
/// Merge behaviour
/// </container>
Fit.Core.MergeOverwriteBehaviour = // Enums must exist runtime
{
/// <member container="Fit.Core.MergeOverwriteBehaviour" name="Always" access="public" static="true" type="string" default="Always">
/// <description> Always overwrite property values from target object with property values from merge object (default behaviour) </description>
/// </member>
Always: "Always",
/// <member container="Fit.Core.MergeOverwriteBehaviour" name="SkipNullAndUndefined" access="public" static="true" type="string" default="SkipNullAndUndefined">
/// <description> Always overwrite property values from target object with property values from merge object, except values from merge object that are Null or Undefined </description>
/// </member>
SkipNullAndUndefined: "SkipNullAndUndefined",
/// <member container="Fit.Core.MergeOverwriteBehaviour" name="Never" access="public" static="true" type="string" default="Never">
/// <description>
/// Never overwrite property values from target object - only add missing property values from merge object
/// </description>
/// </member>
Never: "Never"
};
/// <function container="Fit.Core" name="Merge" access="public" static="true" returns="$ObjectTypeA + $ObjectTypeB">
/// <description>
/// Deep merges two objects and returns the resulting object.
/// Take notice of the behaviour and restriction of Fit.Core.Clone(..) since
/// the target object is first cloned using that function. The resulting object is
/// then enriched with the data from the merge object.
/// Property values on the merge object takes precedence over property values on the
/// target object. Arrays are not merged but merely replaced if defined on the merge object.
/// </description>
/// <param name="targetObject" type="$ObjectTypeA"> Target object </param>
/// <param name="mergeObject" type="$ObjectTypeB"> Merge object </param>
/// <param name="mergeObjectOverwriteBehaviour" type="Fit.Core.MergeOverwriteBehaviour" default="undefined"> Overwrite behaviour for merge object </param>
/// </function>
Fit.Core.Merge = function(targetObject, mergeObject, mergeObjectOverwriteBehaviour)
{
Fit.Validation.ExpectObject(targetObject);
Fit.Validation.ExpectObject(mergeObject);
Fit.Validation.ExpectStringValue(mergeObjectOverwriteBehaviour, true);
/* // Test data
f1 = function() { alert("Hello"); };
f2 = function() { alert("Hello2"); };
x = {
str: "Hello world",
num: 123,
dec: 123.321,
num2: 1,
num3: parseFloat("abc"),
date: new Date("2014-12-01 13:02:23"),
bool: true,
bool2: false,
arr: [100, 200, 250, 400],
arr2: ["Hello", "world"],
arr3: [123, "hello", true, false, new Date("1990-01-20"), [1,2,3], { x: { "hapsen": f1, "hello": new Array(1,2,3) } }],
obj: { a: 123, b: 123.321, c: true, d: false, e: new Date("1993-06-25"), f: "hello", g: null, h: undefined, propNotFoundOnMergeObj: { name: "one", age: 11 } },
specialProp: { x: true, y: undefined, z: null, date: new Date() },
oneUn: undefined
};
y = {
str: "Hello world 2",
num: 222,
dec: 222.222,
num2: parseInt("abc"),
num3: 22.2,
date: new Date("2222-02-02 22:22:22"),
bool: false,
bool2: true,
arr: [2, 22, 222, 2222, 22222, 2222222222],
arr2: ["Hello2", "world2"],
arr3: [222, "hello2", false, true, new Date("2002-02-02"), [2], { x: { "hapsen2": f2, "hello2": new Array(2,2,2,2,2,2,2,2,2,2) } }],
obj: { a: 2, b: 2.2, c: false, d: true, e: new Date("2202-02-22"), f: "hello2", g: {}, h: null, newProp: { name: "two", age: 22, gender: "female" } },
two: { x: 2, y: true, z: undefined },
twoUn: undefined
};
var backupX = Fit.Core.Clone(x);
var backupY = Fit.Core.Clone(y);
var merged1 = Fit.Core.Merge(x, y);
var merged2 = Fit.Core.Merge(y, x);
console.log(merged1);
console.log(merged2);
console.log("Merges equal:", Fit.Core.IsEqual(merged1, merged2)); // Expecting False
console.log("X untouched:", Fit.Core.IsEqual(x, backupX)); // Expecting True
console.log("Y untouched:", Fit.Core.IsEqual(y, backupY)); // Expecting True*/
var isObject = function(val)
{
return (val !== undefined && val !== null && typeof(val) === "object" && (val instanceof Date) === false && (val instanceof Array) === false);
}
var newObject = Fit.Core.Clone(targetObject);
Fit.Array.ForEach(mergeObject, function(prop)
{
if (isObject(newObject[prop]) && isObject(mergeObject[prop]))
{
newObject[prop] = Fit.Core.Merge(newObject[prop], mergeObject[prop], mergeObjectOverwriteBehaviour);
}
else
{
if (mergeObjectOverwriteBehaviour === Fit.Core.MergeOverwriteBehaviour.SkipNullAndUndefined && (mergeObject[prop] === null || mergeObject[prop] === undefined))
{
return; // Skip - ignore values of Null and Undefined from merge object
}
else if (mergeObjectOverwriteBehaviour === Fit.Core.MergeOverwriteBehaviour.Never && prop in newObject)
{
return; // Skip - never update values from target object, only add missing values
}
newObject[prop] = mergeObject[prop];
}
});
return newObject;
}
/// <function container="Fit.Core" name="Clone" access="public" static="true" returns="$ObjectType">
/// <description>
/// Clone JavaScript object. Supported object types and values:
/// String, Number, Boolean, Date, Array, (JSON) Object, Function, Undefined, Null, NaN,
/// Infinity.
/// Variables defined as undefined are left out of clone,
/// since an undefined variable is equal to a variable defined as undefined.
/// Notice that Arrays and Objects can contain supported object types and values only.
/// Functions are considered references, and as such the cloned object will reference
/// the same functions.
/// Custom properties set on native JS objects (e.g. Array.XYZ) are not cloned, only
/// values are. Naturally custom (JSON) objects will be fully cloned, including all
/// properties. Both arrays and custom (JSON) objects are cloned recursively.
/// Be aware of self referencing variables and circular structures, which
/// will cause an infinite loop, and eventually a stack overflow exception.
/// DOM objects and window/frame instances are not supported.
/// </description>
/// <param name="obj" type="$ObjectType"> JS object to clone </param>
/// </function>
Fit.Core.Clone = function(obj)
{
// TODO - Known problem:
// var a = new SomeClass();
// var b = (a instanceOf SomeClass);
// var c = (SMCore.Clone(a) instanceOf SomeClass);
// Variable b is True as expected, while variable c is False!
// TODO: Restore/preserve support for instanceof!
// TEST CASE: Example below is supposed to return: TRUE!
/*var f1 = function() { alert("Hello"); }
var x =
{
str: "Hello world",
num: 123,
dec: 123.321,
date: new Date("2014-12-01 13:02:23"),
bool: true,
bool2: false,
arr: [100, 200, 250, 400],
arr2: ["Hello", "world"],
arr3: [123, "hello", true, false, new Date("1990-01-20"), [1,2,3], { x: { "hapsen": f1, "hello": new Array(1,2,3) } }],
obj: { a: 123, b: 123.321, c: true, d: false, e: new Date("1993-06-25"), f: "hello", g: null, h: undefined }
};
var y = SMCore.Clone(x);
console.log("Is equal: " + SMCore.IsEqual(x, y));*/
// Clone object by serializing it into a JSON string, and parse it back into a JS object
var serialized = JSON.stringify(obj); // Returns undefined if obj is either undefined or a function (these are not serialized)
var clone = ((serialized !== undefined) ? JSON.parse(serialized) : serialized); // parse(..) throws error if argument is undefined
// Fixes
// - Dates are serialized into strings - turn back into Date instances.
// - Functions are not serialized (discarded) - add function reference to clone
// - Number variables with a value of NaN is serialized into Null - convert to NaN
var fixClone = null;
fixClone = function(org, clo)
{
if (org instanceof Date) // Dates are turned into string representations - turn back into Date instances
{
return new Date(org.getTime());
}
else if (typeof(org) === "function") // Functions are not serialized - use same reference as original object
{
return org;
}
else if (typeof(org) === "number" && isNaN(org) === true) // NaN is turned into Null - turn back into NaN
{
return parseInt("");
}
else if (typeof(org) === "number" && org === Infinity) // Infinity is turned into Null - turn back into Infinity
{
return Infinity;
}
else if (org instanceof RegExp)
{
var flags = "";
if (org.ignoreCase)
flags += "i";
if (org.global)
flags += "g";
if (org.multiline)
flags += "m";
if (org.sticky) // Notice that sticky is not supported in legacy IE
flags += "y";
return new RegExp(org.source, flags);
}
else if (org && typeof(org) === "object") // Recursively fix children (object/array)
{
for (var p in org)
clo[p] = fixClone(org[p], clo[p]);
}
return clo;
};
clone = fixClone(obj, clone);
// Done, clone is now identical to original object - SMCore.IsEqual(obj, clone) should return True
return clone;
}
// INTERNAL
Fit._internal =
{
Core:
{
VersionInfo: { Major: 3, Minor: 2, Patch: 12 } // Do NOT modify format - version numbers are programmatically changed when releasing new versions - MUST be on a separate line!
}
};
Fit._internal.Core.EnsureStyles = function()
{
if (Fit._internal.Core.StylesEnsured === true)
return;
Fit._internal.Core.StylesEnsured = true;
Fit.Events.OnDomReady(function() // In case function is called too early in which case document.body will be null
{
var elm = Fit.Dom.CreateElement("<div class='FitUiStyleCheck'></div>");
Fit.Dom.Add(document.body, elm);
if (Fit.Dom.GetComputedStyle(elm, "width") !== "20px")
{
Fit.Browser.Log("Lazy loading Fit.UI stylesheet. It is recommended to add a stylesheet reference to Fit.UI.min.css to prevent temporarily unstyled content.");
Fit.Loader.LoadStyleSheet(Fit.GetUrl() + "/Fit.UI.min.css?cacheKey=" + Fit.GetVersion().Version);
}
Fit.Dom.Remove(elm);
});
}
;(function()
{
// Find Base URL - e.g. http://server.com/libs/fitui
// Trying to aquire Fit.UI script by ID first, as this is most reliable. Obtaining the last script from the
// scripts collection is less reliable as some browser extensions manipulate the collection while scripts are loading.
var script = document.querySelector("script#FitUI") || document.querySelector("script#fitui") || document.scripts[document.scripts.length - 1];
var src = script && script.src || null;
if (!src)
{
// Fit.UI was loaded dynamically as a module or bundled with e.g. WebPack.
src = location.href;
}
Fit._internal.BaseUrl = src.substring(0, src.lastIndexOf("/"));
// Calculate Base Path - e.g. / (unlikely scenario having Fit.UI located at the root though) or /libs/fitui
var path = Fit._internal.BaseUrl.replace("http://", "").replace("https://", "");
Fit._internal.BasePath = ((path.indexOf("/") !== -1) ? path.substring(path.indexOf("/")) : "/");
})();
/// <container name="Fit.CoreTypeDefs.VersionInfo">
/// <description> Version information </description>
/// <member name="Major" type="integer"> Major version number </member>
/// <member name="Minor" type="integer"> Minor version number </member>
/// <member name="Patch" type="integer"> Patch version number </member>
/// <member name="Version" type="string"> String version number in the format Major.Minor.Patch </member>
/// </container>
/// <function container="Fit" name="GetVersion" access="public" static="true" returns="Fit.CoreTypeDefs.VersionInfo">
/// <description>
/// Get Fit.UI version object containing the following properties:
/// Major (integer), Minor (integer), Patch (integer), Version (string representing Major.Minor.Patch).
/// </description>
/// </function>
Fit.GetVersion = function()
{
var info = Fit.Core.Clone(Fit._internal.Core.VersionInfo);
info.Version = info.Major + "." + info.Minor + "." + info.Patch;
return info;
}
/// <function container="Fit" name="GetUrl" access="public" static="true" returns="string">
/// <description> Get fully qualified URL to Fit.UI on server - e.g. http://server.com/libs/fitui </description>
/// </function>
Fit.GetUrl = function()
{
if (Fit._internal.BaseUrlOverride !== undefined)
return Fit._internal.BaseUrlOverride;
return Fit._internal.BaseUrl;
}
/// <function container="Fit" name="GetPath" access="public" static="true" returns="string">
/// <description> Get absolute path to Fit.UI on server - e.g. /libs/fitui </description>
/// </function>
Fit.GetPath = function()
{
if (Fit._internal.BasePathOverride !== undefined)
return Fit._internal.BasePathOverride;
return Fit._internal.BasePath;
}
/// <function container="Fit" name="SetPath" access="public" static="true">
/// <description>
/// Set path to Fit.UI on server - e.g. libs/fitui.
/// This may be necessary if Fit.UI is loaded dynamically
/// using RequireJS or bundled using e.g. WebPack.
/// Changing the path affects the return value of both
/// GetUrl() and GetPath(), and from where Fit.UI will
/// load resources dynamically.
/// </description>
/// <param name="basePath" type="string"> Absolute or relative path to folder containing Fit.UI </param>
/// </function>
Fit.SetPath = function(basePath)
{
Fit.Validation.ExpectStringValue((basePath === null ? "-" : basePath));
if (basePath === null)
{
delete Fit._internal.BasePathOverride;
delete Fit._internal.BaseUrlOverride;
return;
}
// Remove trailing slash if found
if (basePath !== "/" && basePath.lastIndexOf("/") === basePath.length - 1)
{
basePath = basePath.substring(0, basePath.length - 1);
}
var rootUrl = location.protocol + "//" + location.host; // E.g. http://my-domain.com
// Both GetPath() and GetUrl() return values without trailing slashes.
// E.g. libs/fitui and http://host/libs/fitui.
// However, when installed to the root, this is indicated by a slash returned
// from GetPath() while GetUrl() keeps returning a URL without a trailing slash.
// E.g. / and http://my-domain.com.
Fit._internal.BasePathOverride = basePath;
if (basePath === "/")
{
Fit._internal.BaseUrlOverride = rootUrl; //curUrl;
}
else if (basePath.indexOf("/") === 0) // Absolute path
{
Fit._internal.BaseUrlOverride = rootUrl + basePath;
}
else // Relative path
{
var curPath = location.pathname.substring(0, location.pathname.lastIndexOf("/")); // E.g. "" (empty for root) or /path/to/folder
Fit._internal.BaseUrlOverride = rootUrl + curPath + "/" + basePath;
}
}
/// <function container="Fit" name="SetUrl" access="public" static="true">
/// <description>
/// Set URL to Fit.UI on server - e.g. http://cdn/libs/fitui/.
/// This may be necessary if Fit.UI is loaded dynamically
/// from a foreign domain such as a CDN (Content Delivery Network).
/// Changing the URL affects the return value of both
/// GetUrl() and GetPath(), and from where Fit.UI will
/// load resources dynamically.
/// </description>
/// <param name="baseUrl" type="string"> Full URL to folder containing Fit.UI </param>
/// </function>
Fit.SetUrl = function(baseUrl) // E.g. http://foreign-host/path/to/Fit.UI/
{
Fit.Validation.ExpectStringValue((baseUrl === null ? "-" : baseUrl));
if (baseUrl === null)
{
Fit.SetPath(null); // Reset Path and URL overriding
return;
}
var url = Fit.Browser.ParseUrl(baseUrl);
Fit.SetPath(url.FullPath); // Using FullPath rather than Path in case URL has been specified using http://host/path/to/Fit.UI (without trailing slash which makes Fit.UI a resource and not a folder, resulting in it being excluded from Path)
var newUrl = (url.Url.lastIndexOf("/") === url.Url.length - 1 ? url.Url.substring(0, url.Url.length - 1) : url.Url); // Both GetPath() and GetUrl() return values without trailing slashes - e.g. libs/fitui and http://host/libs/fitui - so make sure no trailing slash is present
Fit._internal.BaseUrlOverride = newUrl;
}
/// <container name="Fit.Validation">
/// Validation logic
/// </container>
Fit.Validation = {};
Fit._internal.Validation = {};
Fit._internal.Validation.DebugMode = true;
Fit._internal.Validation.Clone = null;
// Ensure object types not found in all browsers
(function()
{
// window.event is of type MSEventObj in IE9 and above.
// Type used by Fit.Validation.ExpectEvent.
if (!window.MSEventObj)
window.MSEventObj = function() {};
// StaticNodeList type is produced by document.querySelectorAll(..) in Legacy IE.
// FileList type is not defined in Legacy IE.
// These types are used by Fit._internal.Validation.IsCollectionType.
if (!window.StaticNodeList)
window.StaticNodeList = function() {};
if (!window.FileList)
window.FileList = function() {};
// File type not defined in Legacy IE.
// Make sure we can use Fit.Validation.ExpectInstance(selectedFileFromInput, File, true)
if (!window.File)
window.File = function() {};
// Some versions of Firefox temporarily removed NamedNodeMap.
// They renamed it to MozNamedAttrMap, but it was later restored.
// https://bugzilla.mozilla.org/show_bug.cgi?id=858344
if (!window.NamedNodeMap)
window.NamedNodeMap = function() {};
})();
// ==========================================================
// Type checking
// ==========================================================
/// <function container="Fit.Validation" name="ExpectObject" access="public" static="true">
/// <description> Throws error if passed object is not a valid object such as { Name: 'Jimmy', Age: 34 } </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectObject = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (!val || val.constructor !== Object)
Fit.Validation.ThrowError("Value '" + val + "' is not a valid object");
}
/// <function container="Fit.Validation" name="ExpectNumber" access="public" static="true">
/// <description> Throws error if passed object is not a number </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectNumber = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (typeof(val) !== "number" || isNaN(val) === true)
Fit.Validation.ThrowError("Value '" + val + "' is not a valid number");
}
/// <function container="Fit.Validation" name="ExpectInteger" access="public" static="true">
/// <description> Throws error if passed object is not an integer </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectInteger = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (typeof(val) !== "number" || val % 1 !== 0)
Fit.Validation.ThrowError("Value '" + val + "' is not a valid integer");
}
/// <function container="Fit.Validation" name="ExpectString" access="public" static="true">
/// <description> Throws error if passed object is not a string </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectString = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (typeof(val) !== "string")
Fit.Validation.ThrowError("Value '" + val + "' is not a valid string");
}
/// <function container="Fit.Validation" name="ExpectStringValue" access="public" static="true">
/// <description> Same as Fit.Validation.ExpectString(..), but string must contain an actual value if set (not be empty) </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectStringValue = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
Fit.Validation.ExpectString(val);
if (val === "")
Fit.Validation.ThrowError("String cannot be empty");
}
/// <function container="Fit.Validation" name="ExpectBoolean" access="public" static="true">
/// <description> Throws error if passed object is not a boolean </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectBoolean = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (typeof(val) !== "boolean")
Fit.Validation.ThrowError("Value '" + val + "' is not a valid boolean");
}
/// <function container="Fit.Validation" name="ExpectDate" access="public" static="true">
/// <description> Throws error if passed object is not an instance of Date </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectDate = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Date) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an instance of Date");
}
/// <function container="Fit.Validation" name="ExpectArray" access="public" static="true">
/// <description> Throws error if passed object is not an instance of Array </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectArray = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Array) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an instance of Array");
}
/// <function container="Fit.Validation" name="ExpectTypeArray" access="public" static="true">
/// <description>
/// Throws error if passed object is not an instance of Array
/// contaning only objects/values of type given by validation callback.
/// Example: Fit.Validation.ExpectTypeArray(arr, Fit.Validation.ExpectString)
/// </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="typeValCallback" type="function"> Value validation callback </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectTypeArray = function(val, typeValCallback, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Array) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an instance of Array");
// Validate types within array
Fit.Validation.ExpectFunction(typeValCallback); // Make sure callback is valid
Fit.Array.ForEach(val, function(v)
{
typeValCallback(v);
});
}
/// <function container="Fit.Validation" name="ExpectInstanceArray" access="public" static="true">
/// <description>
/// Throws error if passed object is not an instance of Array
/// contaning only instances of specified type. Example:
/// Fit.Validation.ExpectInstanceArray(arr, Fit.Controls.TreeViewNode)
/// </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="instanceType" type="object"> Instance type (constructor, e.g. Fit.Http.Request) </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectInstanceArray = function(val, instanceType, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Array) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an instance of Array");
// Validate types within array
Fit.Array.ForEach(val, function(v)
{
Fit.Validation.ExpectInstance(v, instanceType);
});
}
/// <function container="Fit.Validation" name="ExpectDictionary" access="public" static="true">
/// <description>
/// Throws error if passed object is not a dictionary (associative array / object array),
/// contaning only objects/values of type given by validation callback.
/// Example: Fit.Validation.ExpectDictionary(dict, Fit.Validation.ExpectString)
/// </description>
/// <param name="val" type="object"> Dictionary to validate </param>
/// <param name="typeValCallback" type="function"> Value validation callback </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectDictionary = function(val, typeValCallback, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
Fit.Validation.ExpectObject(val);
Fit.Validation.ExpectFunction(typeValCallback);
Fit.Array.ForEach(val, function(key)
{
Fit.Validation.ExpectStringValue(key);
typeValCallback(val[key]);
});
}
/// <function container="Fit.Validation" name="ExpectCollection" access="public" static="true">
/// <description> Throws error if passed object is not a collection that can be iterated </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectCollection = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (Fit._internal.Validation.IsCollectionType(val) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not a valid collection");
}
/// <function container="Fit.Validation" name="ExpectRegExp" access="public" static="true">
/// <description> Throws error if passed object is not an instance of RegExp </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectRegExp = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (val instanceof RegExp === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an instance of RegExp");
}
/// <function container="Fit.Validation" name="ExpectElement" access="public" static="true">
/// <description> Throws error if passed object is not an instance of HTMLElement </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectElement = function(val, allowNotSet) // DOMElement (actually HTMLElement)
{
// All elements within the HTML DOM tree inherits from Element except for Comments, CDATA, and Text nodes.
// But most elements from an XML document (also excluding Comments, CDATA, and Text nodes) also
// inherit from Element.
// (new DOMParser()).parseFromString("<root></root>", "text/xml").getElementsByTagName("root")[0] instanceof Element; // Returns true
// However, Fit.UI is for building HTML applications, and is therefore not expected to handle XML elements.
// Therefore, despite the function name ExpectElement, it actually ensures that the passed element is an instance of HTMLElement.
// (new DOMParser()).parseFromString("<root></root>", "text/xml").getElementsByTagName("root")[0] instanceof HTMLElement; // Returns false
// More about HTMLElement vs Element vs Node, as well as the nodeType property:
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
// https://developer.mozilla.org/en-US/docs/Web/API/Element
// https://developer.mozilla.org/en-US/docs/Web/API/Node
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
// The inheritance chain looks like this: EventTarget <= Node <= Element <= HTMLElement
if (allowNotSet === true && (val === undefined || val === null))
return;
// The HTMLElement interface is not available in legacy IE, in which case we use feature detection instead
if ((window.HTMLElement && (val instanceof HTMLElement) === false) || (val instanceof Element) === false || (val.style instanceof CSSStyleDeclaration) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an HTMLElement");
}
Fit.Validation.ExpectDomElement = Fit.Validation.ExpectElement; // Backward compatibility
Fit.Validation.ExpectElementNode = Fit.Validation.ExpectElement; // Backward compatibility
// DISABLED - not in use internally, and not supported by legacy IE
// <function container="Fit.Validation" name="ExpectTextNode" access="public" static="true">
// <description> Throws error if passed object is not an instance of Text </description>
// <param name="val" type="object"> Object to validate </param>
// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
// </function>
/*Fit.Validation.ExpectTextNode = function(val, allowNotSet) // DOMText
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Text) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not a Text node");
}*/
// DISABLED - not in use internally, and not supported by legacy IE
// <function container="Fit.Validation" name="ExpectCommentNode" access="public" static="true">
// <description> Throws error if passed object is not an instance of Comment </description>
// <param name="val" type="object"> Object to validate </param>
// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
// </function>
/*Fit.Validation.ExpectCommentNode = function(val, allowNotSet) // DOMComment
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Comment) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not a Comment node");
}*/
/// <function container="Fit.Validation" name="ExpectNode" access="public" static="true">
/// <description> Throws error if passed object is not an instance of Element, Text, or Comment </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectNode = function(val, allowNotSet) // DOMNode
{
if (allowNotSet === true && (val === undefined || val === null))
return;
// The Text and Comment interfaces are not available in legacy IE so we use feature detection instead.
// We assume we have a valid Node instance if it exposes common functions and properties of a Node instance,
// and has the appropriate nodeType: 1 = Element, 3 = Text, 8 = Comment. More on node types here:
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
if (!val.cloneNode || !val.nodeName || !val.nodeType || (val.nodeType !== 1 && val.nodeType !== 3 && val.nodeType !== 8))
Fit.Validation.ThrowError("Value '" + val + "' is not a Node (Element, Text, or Comment)");
}
// <function container="Fit.Validation" name="ExpectTextNode" access="public" static="true">
// <description> Throws error if passed object is not an instance of Text </description>
// <param name="val" type="object"> Object to validate </param>
// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
// </function>
Fit.Validation.ExpectTextNode = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
// The Text interface is not available in legacy IE so we use feature detection instead.
// We assume we have a valid Node instance if it exposes common functions and properties of a Node instance,
// and has the appropriate nodeType: 1 = Element, 3 = Text, 8 = Comment. More on node types here:
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
if (!val.cloneNode || !val.nodeName || val.nodeType !== 3)
Fit.Validation.ThrowError("Value '" + val + "' is not a Text node");
}
// <function container="Fit.Validation" name="ExpectContentNode" access="public" static="true">
// <description> Throws error if passed object is not an instance of Element or Text </description>
// <param name="val" type="object"> Object to validate </param>
// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
// </function>
/*Fit.Validation.ExpectContentNode = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Element) === false && (val instanceof Text) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not a Content Node (Element or Text)");
}*/
/// <function container="Fit.Validation" name="ExpectWindow" access="public" static="true">
/// <description> Throws error if passed object is not an instance of Window </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectWindow = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if ((val instanceof Window) === false)
Fit.Validation.ThrowError("Value '" + val + "' is not an instance of Window");
}
/// <function container="Fit.Validation" name="ExpectFunction" access="public" static="true">
/// <description> Throws error if passed object is not a valid function </description>
/// <param name="val" type="object"> Object to validate </param>
/// <param name="allowNotSet" type="boolean" default="false"> Set True to allow object to be Null or Undefined </param>
/// </function>
Fit.Validation.ExpectFunction = function(val, allowNotSet)
{
if (allowNotSet === true && (val === undefined || val === null))
return;
if (typeof(val) !== "function")
Fit.Validation.ThrowError("Value '" + val + "' is not a valid function");
}
/// <function container="Fit.Validation" name="ExpectEventTarget" access="public" static="true">
/// <description> Throws error if passed object is not an instance of EventT