// SMEnvironment
///
/// Sitemagic environment information
///
SMEnvironment = function()
{
}
///
/// Returns path to files directory
///
SMEnvironment.GetFilesDirectory = function()
{
return ((window.SMClientEnvironmentInfo !== undefined) ? SMClientEnvironmentInfo.Dirs.Files : "files");
}
///
/// Returns path to data directory
///
SMEnvironment.GetDataDirectory = function()
{
return ((window.SMClientEnvironmentInfo !== undefined) ? SMClientEnvironmentInfo.Dirs.Data : "data");
}
///
/// Returns path to images directory
///
SMEnvironment.GetImagesDirectory = function()
{
return ((window.SMClientEnvironmentInfo !== undefined) ? SMClientEnvironmentInfo.Dirs.Images : "images");
}
///
/// Returns path to templates directory
///
SMEnvironment.GetTemplatesDirectory = function()
{
return ((window.SMClientEnvironmentInfo !== undefined) ? SMClientEnvironmentInfo.Dirs.Templates : "templates");
}
///
/// Returns path to extensions directory
///
SMEnvironment.GetExtensionsDirectory = function()
{
return ((window.SMClientEnvironmentInfo !== undefined) ? SMClientEnvironmentInfo.Dirs.Extensions : "extensions");
}
///
/// Returns flag indicating whether current site is a subsite
///
SMEnvironment.IsSubSite = function()
{
return ((window.SMClientEnvironmentInfo !== undefined) ? SMClientEnvironmentInfo.IsSubSite : false);
}
// SMLanguageHandler
///
/// Language support for client API
///
SMLanguageHandler = function()
{
}
///
/// Returns translation for specified language string - returns empty string if not found
/// Specify language key for desired translation
///
SMLanguageHandler.GetTranslation = function(translation)
{
return SMStringUtilities.UnicodeDecode(((SMClientLanguageStrings[translation] !== undefined) ? SMClientLanguageStrings[translation] : ""));
}
// SMStringUtilities
///
/// String manipulation functionality not provided natively by JavaScript
///
function SMStringUtilities()
{
}
///
/// Returns True if string starts with specified search expression, otherwise False
/// String to search
/// String to search for
///
SMStringUtilities.StartsWith = function(str, search)
{
return (str.indexOf(search) === 0);
}
///
/// Returns True if string ends with specified search expression, otherwise False
/// String to search
/// String to search for
///
SMStringUtilities.EndsWith = function(str, search)
{
return (str.length >= search.length && str.substring(str.length - search.length) === search);
}
///
/// Returns provided string excluding whitespaces in beginning and end of string
/// String to trim
///
SMStringUtilities.Trim = function(str)
{
var whitespaces = new Array(" ", "\n", "\r", "\t");
var changed = true;
while (changed === true)
{
changed = false;
for (var i = 0 ; i < whitespaces.length ; i++)
{
while (str.substring(0, 1) === whitespaces[i])
{
str = str.substring(1, str.length);
changed = true;
}
while (str.substring(str.length - 1, str.length) === whitespaces[i])
{
str = str.substring(0, str.length - 1);
changed = true;
}
}
}
return str;
}
///
/// Replace all occurences of given value within a string and return the result
/// String to replace within
/// String to replace
/// Replacement string
///
SMStringUtilities.ReplaceAll = function(str, search, replace)
{
return str.split(search).join(replace);
}
///
/// Replace single occurence of given value after a given offset within a string, and return the result
/// String to replace within
/// String to replace
/// Replacement string
/// Perform replacement after specified offset
///
SMStringUtilities.Replace = function(str, search, replace, offset)
{
var startText = str.substring(0, offset);
var endText = str.substring(offset);
endText = endText.replace(search, replace);
return startText + endText;
}
///
///
/// Encodes Unicode/UTF-8 characters into HEX entities. For instance the Euro symbol becomes €.
///
/// String to encode
///
SMStringUtilities.UnicodeEncode = function(str) // Also works with Windows-1252 (HTML5 document) - encodes e.g. Euro symbol as expected
{
// Browsers convert characters not compatible with document encoding into HEX entities.
// Unfortunately there are two drawbacks:
// 1) Windows-1252 specific characters are not being encoded into HEX entities when the
// document encoding is set to ISO-8859-1, because the browser assumes we want Windows-1252.
// http://en.wikipedia.org/wiki/Windows-1252:
// "Most modern web browsers and e-mail clients treat the MIME charset ISO-8859-1 as Windows-1252"
// 2) Conversion only takes place on post back, meaning we cannot get the value client side that
// gets posted to the server.
// This function allows us to encode characters not part of ISO-8859-1 into HEX entities client side.
// Encode characters that are not between code point 0 (Unicode HEX 0000) and code point 255 (Unicode HEX 0100).
// Notice that although Windows-1252 specific characters are found between code point 128 and 159 in the
// Windows-1252 character table (http://en.wikipedia.org/wiki/Windows-1252), the browser internally handles
// characters as Unicode, which is the reason why the code below works - Windows-1252 specific characters
// are found outside of code point 0 - 255 in Unicode.
// Unicode/UTF-8 is backward compatible with ASCII, meaning code point 128-159 is empty - so no
// Unicode specific characters in this space is left unhandled.
// Also be aware that JavaScript (ECMAScript prior to version 6) have problems dealing with Unicode Characters
// outside of Basic Multilingual Plane (BMP). Fortunately BMP covers all the popular character sets. However,
// popular symbols like Emojis may not work as expected. The following article describes it well:
// https://mathiasbynens.be/notes/javascript-unicode
// A really simple example of how JavaScript incorrectly handles symbols outside of BMP is to evaluate
// e.g. "x".length in a browser running ECMAScript 5. Replace "x" with the symbol "CLOSED LOCK WITH KEY":
// https://unicode-table.com/en/search/?q=CLOSED+LOCK+WITH+KEY
// Rather than returning a length of 1, it will return a length of 2.
// Because of this, the replace logic below will cause the callback to be invoked twice for symbols
// outside of BMP. "CLOSED LOCK WITH KEY" will produce (Surrogate pair). This will be turned
// back to the "CLOSED LOCK WITH KEY" symbol if injected into an Input field, but unfortunately not if injected
// into an ordinary DOM element such as a . Instead it displays two question marks.
// The lock symbol can actually be represented by a HEX entity (🔐) that works when injected into
// the DOM, but that on the other hand will not work when injection into an Input field.
// Basically it's a problem that JS and DOM has different representations for the same thing. So supporting
// characters outside of BMP is not realistic when using HEX entities to represent Unicode characters - at
// least not with ECMAScript prior to version 6.
//return str.replace(/[^\u0000-\u0100]/g, function(character) { /*console.log("Encoding: " + character);*/ return "" + character.charCodeAt(0) + ";" });
// Encodes all characters extending ASCII - ASCII is compatible with UTF-8, ISO-8859-1 is not.
return str.replace(/[^\x00-\x7F]/g, function(character) { /*console.log("Encoding: " + character);*/ return "" + character.charCodeAt(0) + ";" });
// The example below encodes Windows-1252 specific characters only.
// The Unicode code points are found on the Windows-1252 character table on http://en.wikipedia.org/wiki/Windows-1252
// Notice how Windows-1252 specific characters are highlighted with thick green borders.
// A string consisting of these characters are found in this pastebin: http://pastebin.com/ZR2SW2dL
//return str.replace(/[\u20AC|\u201A|\u0192|\u201E|\u2026|\u2020|\u2021|\u02C6|\u2030|\u0160|\u2039|\u0152|\u017D|\u2018|\u2019|\u201C|\u201D|\u2022|\u2013|\u2014|\u02DC|\u2122|\u0161|\u203A|\u0153|\u017E|\u0178]/g, function(character) { console.log("Encoding: " + character); return "" + character.charCodeAt(0) + ";" });
}
///
///
/// Decodes string containing Unicode HEX entities into ordinary text and returns the result.
///
/// String to decode
///
SMStringUtilities.UnicodeDecode = function(str)
{
return str.replace(/\d+;/g, function(entity) // Match followed by one or more digits followed by semicolon. (?<=)\d+(?=;) would be better, but JS only supports Look Ahead, so let's just substring and the semicolon away.
{
/*console.log("Decoding: " + entity);*/
return String.fromCharCode(entity.substring(2, entity.length - 1));
});
}
// SMColor
function SMColor()
{
}
///
/// Convert RGB colors into HEX color string - returns null in case of invalid RGB values
/// Color index for red
/// Color index for green
/// Color index for blue
///
SMColor.RgbToHex = function(r, g, b)
{
if (typeof(r) !== "number" || typeof(g) !== "number" || typeof(b) !== "number")
return null;
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
return null;
var rHex = r.toString(16);
var gHex = g.toString(16);
var bHex = b.toString(16);
return ("#" + ((rHex.length === 1) ? "0" : "") + rHex + ((gHex.length === 1) ? "0" : "") + gHex + ((bHex.length === 1) ? "0" : "") + bHex).toUpperCase();
}
///
/// Convert HEX color string into RGB color object, e.g. { Red: 150, Green: 30, Blue: 185 } - returns null in case of invalid HEX value
/// HEX color string, e.g. #C0C0C0 (hash symbol is optional)
///
SMColor.ParseHex = function(hex)
{
var result = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
if (result !== null)
return { Red: parseInt(result[1], 16), Green: parseInt(result[2], 16), Blue: parseInt(result[3], 16) };
return null;
}
///
///
/// Parses RGB(A) from string and turns result into RGB(A) color object, e.g.
/// { Red: 100, Green: 100, Blue: 100, Alpha: 0.3 } - returns null in case of invalid value.
///
/// RGB(A) color string, e.g. rgba(100, 100, 100, 0.3) or simply 100,100,200,0.3
///
SMColor.ParseRgb = function(val)
{
// Parse colors from rgb[a](r, g, b[, a]) string
var result = val.match(/\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(\s*,\s*(\d*.*\d+))*/); // http://regex101.com/r/rZ7rO2/9
if (result === null)
return null;
var c = {};
c.Red = parseInt(result[1]);
c.Green = parseInt(result[2]);
c.Blue = parseInt(result[3]);
c.Alpha = ((result[5] !== undefined) ? parseFloat(result[5]) : 1.00);
return c;
}
// SMCore
///
/// Features extending the capabilities of native JavaScript
///
function SMCore()
{
}
///
///
/// Iterates through elements in array and passes each value to the provided callback function.
///
/// Array containing values to iterate through
///
/// Callback function accepting values from the array, passed in turn.
/// Return False from callback to break loop.
///
///
///
///
/// Iterates through object properties and passes each property name to the provided callback function.
///
/// Object containing properties to iterate through
///
/// Callback function accepting properties from the object, passed in turn.
/// Return False from callback to break loop.
///
///
SMCore.ForEach = function(obj, callback)
{
if (obj instanceof Array || typeof(obj.length) === "number") // Array or DOMNodeList
{
for (var i = 0 ; i < obj.length ; i++)
if (callback(obj[i]) === false)
break;
}
else // Object
{
for (var i in obj)
if (callback(i) === false)
break;
}
}
///
///
/// Iterates through given array to find specified object or
/// value, and return its index. Returns -1 if entry could not be found.
///
/// Array to search for given value or object
/// Object or value to find index for
///
SMCore.GetIndex = function(arr, obj)
{
if (arr instanceof Array)
{
for (var i = 0 ; i < arr.length ; i++)
if (SMCore.IsEqual(arr[i], obj) === true)
return i;
}
return -1;
}
///
///
/// Clone JavaScript object. Supported object types and values:
/// String, Number, Boolean, Date, Array, (JSON) Object, Function, Undefined, Null, NaN.
/// 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.
///
/// JS object to clone
///
SMCore.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 (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;
}
///
///
/// Compare two JavaScript objects to determine whether they are identical.
/// Returns True if objects are identical (equal), otherwise False.
/// Supported object types and values:
/// String, Number, Boolean, Date, Array, (JSON) Object, Function, Undefined, Null, NaN.
/// Notice that Arrays and Objects can contain supported object types and values only.
/// 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 custom (JSON) objects will be fully compared, including all
/// properties. Both arrays and custom (JSON) objects are compared 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 comparable.
///
/// JS object to compare agains second JS object
/// JS object to compare agains first JS object
///
SMCore.IsEqual = function(jsObj1, jsObj2)
{
// TEST CASE: Example below is supposed to return: TRUE!
/*var f1 = function() { alert("Hello"); }
var f2 = f1;
SMCore.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 (SMCore.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"
{
for (var k in jsObj1)
if (SMCore.IsEqual(jsObj1[k], jsObj2[k]) === false)
return false;
return true;
}
else if (typeof(jsObj1) === "function" && typeof(jsObj2) === "function")
{
// Returns True in the following situation:
// var f1 = function() { alert("Hello"); }
// var f2 = f1;
// SMCore.IsEqual(f1, f2);
// Returns False in the following situation:
// var f1 = function() { alert("Hello"); }
// var f2 = function() { alert("Hello"); }
// SMCore.IsEqual(f1, f2);
return (jsObj1 === jsObj2);
}
return false;
}
// SMDom
///
/// DOM (Document Object Model) manipulation and helper functionality
///
function SMDom()
{
}
///
/// Add CSS class to element if not already found
/// Element on which CSS class is to be added
/// CSS class name
///
SMDom.AddClass = function(elm, cls)
{
if (SMDom.HasClass(elm, cls) === false)
elm.className += ((elm.className !== "") ? " " : "") + cls;
}
///
/// Remove CSS class from element if found
/// Element from which CSS class is to be removed
/// CSS class name
///
SMDom.RemoveClass = function(elm, cls)
{
var arr = elm.className.split(" ");
var newCls = "";
SMCore.ForEach(arr, function(item)
{
if (item !== cls)
newCls += ((newCls !== "") ? " " : "") + item;
});
elm.className = newCls;
}
///
/// Check whether given DOMElement has specified CSS class registered - returns True if found, otherwise False
/// Element for which CSS class may be registered
/// CSS class name
///
SMDom.HasClass = function(elm, cls)
{
var arr = elm.className.split(" ");
var found = false;
SMCore.ForEach(arr, function(item)
{
if (item === cls)
{
found = true;
return false; // Stop loop
}
});
return found;
}
///
///
/// Get style value applied after stylesheets have been loaded.
/// An empty string may be returned if style has not been defined, or Null if style does not exist.
/// Element which contains desired CSS style value
/// CSS style property name
///
SMDom.GetComputedStyle = function(elm, style)
{
var res = null;
if (window.getComputedStyle)
{
res = window.getComputedStyle(elm)[style];
}
else if (elm.currentStyle)
{
res = elm.currentStyle[style];
}
return (res !== undefined ? res : null);
}
///
/// Returns inner HTML value of DOM element with specified ID - Null if not found
/// Unique element ID
///
SMDom.GetInnerValue = function(elmId)
{
var elm = this.GetElement(elmId);
if (elm === null)
return null;
return elm.innerHTML;
}
///
///
/// Set attribute value on specified DOM element.
/// This function is used to ensure that attributes are handled identically by different browsers.
/// Returns True on success, otherwise False.
///
/// Unique element ID
/// Attribute name
/// Attribute value
///
SMDom.SetAttribute = function(elmId, attr, value)
{
var elm = this.GetElement(elmId);
if (elm === null)
return false;
// Firefox/Safari changes the value of the 'value' and 'checked' attributes, not the visible
// values which IE does. This makes sure the actual values are changed for all browsers.
if (attr.toLowerCase() === "value")
elm.value = value;
else if (attr.toLowerCase() === "checked")
elm.checked = ((value === "true") ? true : false);
else
elm.setAttribute(attr, value);
return true;
}
///
///
/// Returns given attribute value for specified DOM element.
/// This function is used to ensure that attributes are handled identically
/// by different browsers. Returns attribute value on success, otherwise Null.
///
/// Unique element ID
/// Attribute name
///
SMDom.GetAttribute = function(elmId, attr)
{
var elm = this.GetElement(elmId);
if (elm === null)
return null;
// Firefox/Safari returns the value of the 'value' and 'checked' attributes, not the visible
// values which IE does. This makes sure the actual values are returned for all browsers.
if (attr.toLowerCase() === "value")
return elm.value;
else if (attr.toLowerCase() === "checked")
return ((elm.checked === true) ? "true" : "false");
return elm.getAttribute(attr);
}
///
///
/// Set style property on specified DOM element.
/// This function is used to ensure that style properties are properly applied
/// by different browsers. Returns True on success, otherwise False.
///
/// Unique element ID
/// Style property name
/// Property value
///
SMDom.SetStyle = function(elmId, property, value)
{
var elm = this.GetElement(elmId);
if (elm === null)
return false;
if (SMBrowser.GetBrowser() === "MSIE" && SMBrowser.GetVersion <= 7) // IE8 properly supports the display property if a DocType is set, which we assume it is
{
if (property.toLowerCase() === "display" && (value === "inherit" || value === "inline-table" || value === "run-in" || value === "table" || value === "table-caption" || value === "table-cell" || value === "table-column" || value === "table-column-group" || value === "table-row" || value === "table-row-group"))
value = "block";
}
elm.style[property] = value;
return true;
}
///
/// Returns given style property for specified DOM element if found, otherwise Null
/// Unique element ID
/// Style property name
///
SMDom.GetStyle = function(elmId, property)
{
var elm = this.GetElement(elmId);
if (elm === null)
return null;
return elm.style[property];
}
///
/// Returns True if specified DOM element exists, otherwise False
/// Unique element ID
///
SMDom.ElementExists = function(id)
{
var elm = document.getElementById(id);
if (elm !== null)
return true;
else
return false;
}
///
/// Returns specified DOM element if found, otherwise returns Null
/// Unique element ID
///
SMDom.GetElement = function(id)
{
var elm = document.getElementById(id);
if (elm === null)
{
//alert("Unable to get element '" + id + "' - not found");
return null;
}
return elm;
}
///
/// Wraps element in container element while preserving position in DOM
/// Element to wrap
/// Container to wrap element within
///
SMDom.WrapElement = function(elementToWrap, container)
{
var parent = elementToWrap.parentNode;
var nextSibling = elementToWrap.nextSibling;
container.appendChild(elementToWrap); // Causes elementToWrap to be removed from existing container
if (nextSibling === null)
parent.appendChild(container);
else
parent.insertBefore(container, nextSibling);
}
// SMEventHandler
///
/// Event handler functionality
///
function SMEventHandler()
{
}
SMEventHandler.Internal = {};
SMEventHandler.Internal.PageLoaded = false;
///
/// Registers handler for specified event on given DOMElement
/// DOMElement on to which event handler is registered
/// Event name without 'on' prefix (e.g. 'load', 'mouseover', 'click' etc.)
/// JavaScript function delegate
///
SMEventHandler.AddEventHandler = function(element, event, eventFunction)
{
if (element.addEventListener) // W3C
{
element.addEventListener(event, eventFunction, false); // false = event bubbling (reverse of event capturing)
}
else if (element.attachEvent) // IE
{
if (event.toLowerCase() === "domcontentloaded" && SMBrowser.GetBrowser() === "MSIE" && SMBrowser.GetVersion() <= 8)
{
// DOMContentLoaded not supported on IE8.
// Using OnReadyStateChange to achieve similar behaviour.
element.attachEvent("onreadystatechange", function(e)
{
if (element.readyState === "complete")
{
eventFunction(e); // NOTICE: Event argument not identical to argument passed to modern browsers using the real DOMContentLoaded event!
}
});
}
else
{
element.attachEvent("on" + event, eventFunction);
}
}
// Fire event function for onload event if document in window/iframe has already been loaded.
// Notice that no event argument is passed to function since we don't have one.
if (event.toLowerCase() === "load" && element.nodeType === 9 && element.readyState === "complete") // Element is a Document (window.document or iframe.contentDocument)
eventFunction();
else if (event.toLowerCase() === "load" && element.contentDocument && element.contentDocument.readyState === "complete") // Element is an iFrame
eventFunction();
else if (event.toLowerCase() === "load" && element === window && SMEventHandler.Internal.PageLoaded === true) // Element is the current Window instance
eventFunction();
}
;(function()
{
SMEventHandler.AddEventHandler(window, "load", function()
{
SMEventHandler.Internal.PageLoaded = true;
});
})();
// SMCookie
///
/// Cookie functionality
///
function SMCookie()
{
}
///
/// Create or update cookie - returns True on success, otherwise False
/// Unique cookie name
/// Cookie value (cannot contain semi colon!)
/// Expiration time in seconds
///
SMCookie.SetCookie = function(name, value, seconds)
{
if (value.indexOf(';') > -1)
{
//alert("Unable to set cookie - value contains illegal character: ';'");
return false;
}
var date = new Date();
date.setTime(date.getTime() + (seconds * 1000));
var path = location.pathname.match(/^.*\//)[0]; // Examples: / OR /Sitemagic/ OR /Sitemagic/sites/demo/ - https://regex101.com/r/aU8iW6/1
if (SMEnvironment.IsSubSite() === false)
{
// Unfortunately cookies on main site will be accessible by subsites, and also cause naming conflicts.
// Therefore a prefix is made part of the cookie key for the main site.
// This is not necessary for subsites since the cookie path prevent cookies from being shared.
// Example:
// - /Sitemagic: Cookies are available to every sub folder
// - /Sitemagic/sites/demo: Cookies are available to every sub folder, but not parent folders, and therefore not to e.g. /Sitemagic/sites/example
// NOTICE: "SM#/#" prefix MUST be identical to prefix used in SMEnvironment server side!
name = "SM#/#" + name;
}
document.cookie = name + "=" + value + "; expires=" + date.toGMTString() + "; path=" + path;
return true;
}
///
/// Returns cookie value if found, otherwise Null
/// Unique cookie name
///
SMCookie.GetCookie = function(name)
{
if (SMEnvironment.IsSubSite() === false)
{
// Use cookie prefix for main site to prevent conflicts with cookies on subsites.
// NOTICE: "SM#/#" prefix MUST be identical to prefix used in SMEnvironment server side!
name = "SM#/#" + name;
}
var name = name + "=";
var cookies = document.cookie.split(";");
var cookie = null;
for (i = 0 ; i < cookies.length ; i++)
{
cookie = cookies[i];
while (cookie.charAt(0) === " ")
cookie = cookie.substring(1, cookie.length);
if (cookie.indexOf(name) === 0)
return cookie.substring(name.length, cookie.length);
}
return null;
}
///
/// Return names of all cookies
///
SMCookie.GetCookies = function()
{
var cookies = document.cookie.split(";");
var cookie = null;
var info = null;
var names = [];
for (i = 0 ; i < cookies.length ; i++)
{
cookie = cookies[i];
while (cookie.charAt(0) === " ")
cookie = cookie.substring(1, cookie.length);
info = cookie.split("=");
if (SMEnvironment.IsSubSite() === true && info[0].indexOf("SM#/#") === 0) // Exclude main site cookies on subsites
continue;
names.push(((info[0].indexOf("SM#/#") === 0 ? info[0].substring(5) : info[0])));
}
return names;
}
///
/// Remove cookie - returns True on success, otherwise False
/// Unique cookie name
///
SMCookie.RemoveCookie = function(name)
{
return this.SetCookie(name, "", -1);
}
// SMMessageDialog
///
/// Message and dialog functionality
///
function SMMessageDialog()
{
}
///
/// Display message dialog
/// Content of message dialog
///
SMMessageDialog.ShowMessageDialog = function(content)
{
alert(content);
}
///
/// Display message dialog - postpone until onload event is fired
/// Content of message dialog
///
SMMessageDialog.ShowMessageDialogOnLoad = function(content)
{
SMEventHandler.AddEventHandler(window, "load", function() { alert(content); });
}
///
/// Display confirmation dialog. Returns True if user clicks the OK button, False otherwise.
/// Content of confirmation dialog
///
SMMessageDialog.ShowConfirmDialog = function(content)
{
return confirm(content);
}
///
/// Display input dialog. Returns user input as string. Null is returned if user cancels input dialog.
/// Content of confirmation dialog
/// Initial value in input field
///
SMMessageDialog.ShowInputDialog = function(content, value)
{
return prompt(content, value);
}
///
/// Display password dialog. Password entered is passed to callback function.
///
/// JavaScript function delegate fired when user sends password or closes password dialog.
/// Function must take one arguments (string) which is the password entered.
/// The argument will be Null if the password dialog was canceled.
///
/// Optional password dialog title
/// Optional password dialog description
///
SMMessageDialog.ShowPasswordDialog = function(callback, caption, description)
{
var title = (caption !== undefined ? caption : SMLanguageHandler.GetTranslation("EnterPassword"));
var msg = (description !== undefined ? description : "");
// Dialog markup
var content = "";
content += "";
content += "";
content += "";
content += " Password";
content += " ";
content += "";
content += "";
content += "
Password
";
content += " ";
content += "
";
content += "";
content += "";
var smwin = new SMWindow(SMRandom.CreateGuid());
smwin.SetSize(400, ((msg !== "") ? 160 : 125));
smwin.SetContent(content);
smwin.SetOnShowCallback(function()
{
// Get elements
var win = smwin.GetInstance();
var lblHeadline = win.document.getElementsByTagName("h1")[0];
var lblMsg = win.document.getElementsByTagName("div")[0];
var txtPass = win.document.getElementsByTagName("input")[0];
var cmdOk = win.document.getElementsByTagName("input")[1];
var cmdCancel = win.document.getElementsByTagName("input")[2];
// Assign labels (language)
win.document.title = title;
lblHeadline.innerHTML = title;
lblHeadline.style.marginBottom = ((msg !== "") ? "15px" : "20px");
lblMsg.innerHTML = SMStringUtilities.ReplaceAll(SMStringUtilities.ReplaceAll(msg, "\r", ""), "\n", " ");
cmdOk.value = SMLanguageHandler.GetTranslation("Ok");
cmdCancel.value = SMLanguageHandler.GetTranslation("Cancel");
// Event listeners
SMEventHandler.AddEventHandler(cmdOk, "click", function()
{
callback(txtPass.value);
smwin.Close();
});
SMEventHandler.AddEventHandler(cmdCancel, "click", function()
{
callback(null);
smwin.Close();
});
SMEventHandler.AddEventHandler(txtPass, "keydown", function(e)
{
e = (win.event ? win.event : e);
if (e.keyCode === 13)
{
callback(txtPass.value);
smwin.Close();
}
else if (e.keyCode === 27)
{
callback(null);
smwin.Close();
}
});
SMEventHandler.AddEventHandler(window, "beforeunload", function()
{
smwin.Close();
});
txtPass.focus();
});
smwin.SetOnCloseCallback(function()
{
var win = smwin.GetInstance(); // null if already closed, e.g. by using OK/Cancel buttons or ENTER/ESC keys
if (win !== null)
callback(null);
});
smwin.Show();
}
// SMWindow
///
/// Window/dialog functionality.
///
/// // Example of opening a picture in a window
/// var smw = new SMWindow("MyPictureViewer");
/// smw.SetUrl("files/images/Harbour.jpg");
/// smw.SetSize(400, 300);
/// smw.SetOnShowCallback(function() { alert("Window visible"); });
/// smw.SetOnCloseCallback(function() { alert("Window closed"); });
/// smw.Show();
///
/// NOTICE: Sitemagic 2015 introduced some changes to the SMWindow
/// class which might require developers to adjust existing code.
///
/// The new version uses dialogs (inline pop ups) rather than
/// old browser pop ups which are often being blocked, and are
/// broken in many respects. Also, old browser windows are implemented
/// very differently across different browsers, so some options will work
/// on one browser, and not on other browsers.
/// The new Dialog Mode (which is default) is cross browser compatible and
/// will not be blocked.
///
/// It is possible to enable Legacy Mode to keep using the old browser windows, and by
/// that either avoid or minimize changes to existing code. Using Legacy Mode is not
/// recommended though.
/// Legacy Mode can be enabled globally using the config.xml.php configuration file,
/// or per SMWindow instance with a small change to existing code.
///
/// How to globally enable Legacy Mode (always prefer old browser windows):
/// 1) Open config.xml.php for editing
/// 2) Add the following entry and save the file:
/// <entry key="SMWindowLegacyMode" value="True" />
///
/// How to enable Legacy Mode per SMWindow instance in code (use old browser window):
/// - Replace smw.Show() with smw.Show(true) - smw being an instance of SMWindow.
///
/// Code changes that might be required to fully support the new SMWindow class
/// in both Legacy Mode and Dialog Mode:
///
/// - Rather than using window.opener to reference the parent window, use the following
/// snippet to support both the new Dialog Mode and Legacy Mode:
/// var parentWindow = window.opener || window.top;
///
/// - Calling window.close() will not work in Dialog Mode. To work properly in both
/// Dialog Mode and Legacy Mode use the following approach instead:
/// var parentWindow = window.opener || window.top;
/// var smwin = parentWindow.SMWindow.GetInstance(window.name); // SMWindow instance
/// smwin.Close();
/// Be aware that this only works for pages loaded from the same domain (Same-Origin Policy).
/// If the page is loaded from a foreign domain and it needs to be able to close itself, Legacy Mode
/// in conjunction with window.close() must be used.
///
/// - Pages loaded inside an instance of SMWindow cannot reliably use the OnBeforeUnload event since it is only fully
/// supported in Legacy Mode. A page displayed in Dialog Mode can be unloaded by either navigating within the dialog,
/// by closing the dialog (which does not fire OnBeforeUnload), or by reloading the page containing the dialog. This
/// does not map well to the old OnBeforeUnload event. Also the OnBeforeUnload event is not widely and consistently
/// supported by all browsers, and should only be used to ask the user to consider staying on the page. Alternative
/// mechanisms such as persisting data on the fly might be a better solution to prevent users from loosing data when
/// a window is closed. If the OnBeforeUnload event is required to solve a specific problem, forcing Legacy Mode will
/// be necessary.
///
/// - Calling smwin.GetInstance() immediately after calling smwin.Show() is no longer reliable since the window
/// instance may not be immediately ready in Dialog Mode. Instead use the following approach:
/// smwin.SetOnShowCallback(function() { var win = smwin.GetInstance(); /* ...... */ });
/// Use SetOnLoadCallback(..) instead if the DOM document needs to be manipulated.
///
/// Also be aware that the following options are only being used in Legacy Mode:
/// SetDisplayToolBar, SetDisplayLocation, SetDisplayMenuBar, SetDisplayStatusBar.
/// Many browsers, however, do not honor these options anymore. It is recommended to
/// simply avoid using them. Some browsers do not honor these options either in
/// Legacy Mode (e.g. Chrome): SetResizable, SetDisplayScrollBars. They work as
/// expected in Dialog Mode.
///
///
/// Constructor - creates instance of SMWindow
/// Unique instance ID
///
function SMWindow(identifier)
{
// Properties
this.id = (identifier ? identifier : SMRandom.CreateGuid());
this.url = "";
this.content = "";
this.width = 320;
this.height = 240;
this.displayToolBar = false; // Legacy Mode only, not honored by all browsers
this.displayLocation = false; // Legacy Mode only, not honored by all browsers
this.displayMenuBar = false; // Legacy Mode only, not honored by all browsers
this.displayStatusBar = false; // Legacy Mode only, not honored by all browsers
this.displayScrollBars = true; // Not honored by all browsers
this.resizable = true; // Not honored by all browsers
this.positionLeft = 0;
this.positionTop = 0;
this.centerWindow = true;
this.preferModal = false;
this.showCallback = null;
this.closeCallback = null;
this.loadCallback = null;
this.loadCallbackFired = false;
this.instance = null; // Browser window / iFrame Content Window
this.dialog = null; // jQuery dialog (null in Legacy Mode)
// Close existing instance if opened with same identifier
if (SMWindow.instances[this.id] !== undefined)
SMWindow.instances[this.id].Close();
// Add instance to static container which allows us to
// resolve it using the static SMWindow.GetInstance(..) function
SMWindow.instances[this.id] = this;
// Functions
///
/// Get instance ID
///
this.GetId = function()
{
return this.id;
}
///
///
/// Point browser window to specified URL.
/// Specifying a URL to an image (jpg, jpeg, png, or gif) turns the SMWindow instance into
/// an Image Preview Mode which makes the image stretch to the size of the window, and disable scrollbars.
///
/// Valid URL
///
this.SetUrl = function(url)
{
if (SMStringUtilities.EndsWith(url.toLowerCase(), ".jpg") === true || SMStringUtilities.EndsWith(url.toLowerCase(), ".jpeg") === true || SMStringUtilities.EndsWith(url.toLowerCase(), ".png") === true || SMStringUtilities.EndsWith(url.toLowerCase(), ".gif") === true)
{
// Make an image fit within window/dialog, disable scroll, and have image scale when resizing
this.content = "";
this.displayScrollBars = false;
}
else
{
this.url = url;
}
}
///
/// Set browser window content
/// Browser window content (can be HTML)
///
this.SetContent = function(content)
{
this.content = content;
}
///
/// Set browser window dimensions (width and height)
/// Width in pixels
/// Height in pixels
///
this.SetSize = function(width, height)
{
this.width = width;
this.height = height;
}
///
///
/// Determines whether to display browser toolbar or not.
/// Only supported in Legacy Mode, but not by all browsers.
/// Relying on this feature is not recommended.
///
/// Set True to enable toolbar, False not to
///
this.SetDisplayToolBar = function(value)
{
this.displayToolBar = value;
}
///
///
/// Determines whether to display browser location bar or not.
/// Only supported in Legacy Mode, but some browsers will not
/// allow you to disable this for security reasons.
/// Relying on this feature is not recommended.
///
/// Set True to enable location bar, False not to
///
this.SetDisplayLocation = function(value)
{
this.displayLocation = value;
}
///
///
/// Determines whether to display browser menu or not.
/// Only supported in Legacy Mode, but not by all browsers.
/// Relying on this feature is not recommended.
///
/// Set True to enable menu, False not to
///
this.SetDisplayMenuBar = function(value)
{
this.displayMenuBar = value;
}
///
///
/// Determines whether to display browser status bar or not.
/// Only supported in Legacy Mode, but some browsers will not
/// allow you to disable this for security reasons.
/// Relying on this feature is not recommended.
///
/// Set True to enable status bar, False not to
///
this.SetDisplayStatusbar = function(value)
{
this.displayStatusbar = value;
}
///
///
/// Determines whether to display scrollbars or not.
/// Some browsers will not allow you to disable this in Legacy Mode.
///
/// Set True to enable scrollbars, False not to
///
this.SetDisplayScrollBars = function(value)
{
this.displayScrollBars = value;
}
///
///
/// Determines whether user will be able to change size of browser window or not.
/// Some browsers will not allow you to disable this in Legacy Mode.
///
/// Set True to enable resizing, False not to
///
this.SetResizable = function(value)
{
this.resizable = value;
}
///
/// Determines X,Y position of browser window when opened
/// Pixels from left
/// Pixels from top
///
this.SetPosition = function(pixelsLeft, pixelsTop)
{
this.positionLeft = pixelsLeft;
this.positionTop = pixelsTop;
this.centerWindow = false;
}
///
/// Determines whether to center browser window or not
/// Set True to center window, False not to
///
this.SetCenterWindow = function(value)
{
this.centerWindow = value;
}
///
/// Determines whether to make dialog modal or not - this is ignored in Legacy Mode
/// Set True to make dialog modal, False not to
///
this.SetModal = function(value)
{
this.preferModal = value;
}
///
/// Set OnShow callback function to be executed when window is opened
/// Event handler function to execute - takes no arguments
///
this.SetOnShowCallback = function(cb)
{
if (typeof(cb) !== "function")
throw "Callback must be a function";
this.showCallback = cb;
}
///
///
/// Set OnLoad callback function to be executed when page has been loaded.
/// Notice that this does not work for foreign domains in Legacy Mode (Same-Origin Policy).
///
/// Event handler function to execute - takes no arguments
///
this.SetOnLoadCallback = function(cb)
{
// Browser bug: Will not fire in IE (Legacy Mode) if page is being redirected (e.g. domain.com/demo => domain.com/demo/ or index.php => / or index.html => /).
if (typeof(cb) !== "function")
throw "Callback must be a function";
this.loadCallback = cb;
}
///
/// Set OnClose callback function to be executed when window is closed
/// Event handler function to execute - takes no arguments
///
this.SetOnCloseCallback = function(cb)
{
if (typeof(cb) !== "function")
throw "Callback must be a function";
this.closeCallback = cb;
}
///
///
/// Open browser window/dialog.
///
/// Pages opened within the window can use the following approach to access the parent window:
/// var parentWindow = window.opener || window.top; // Browser window instance
/// The page can also access its own instance of SMWindow like so:
/// var smwin = parentWindow.SMWindow.GetInstance(window.name); // SMWindow instance
/// This is only possible for pages loaded on the same domain due to the Same-Origin Policy.
///
///
/// Set True to force use of native browser window (likely to initially be blocked by browser) - not recommended.
/// Set False to force use of Dialog Mode, in case Legacy Mode has been globally enabled, but is not desired for this specific instance.
/// In general it is not recommended to force either Dialog Mode or Legacy Mode.
///
///
this.Show = function(legacyMode)
{
// Close window if already opened (prevent same instance from being opened in both Dialog Mode and Legacy Mode
// if switching legacyMode flag in Show(..) function), and makes sure window always open on top - if window has
// already been opened in Legacy Mode, the window will simply just reload and not emerge on the screen (gain focus).
this.Close();
this.loadCallbackFired = false;
var me = this; // Make "this" scope available to callbacks
// ---------------------------------------
// jQuery UI dialog
// ---------------------------------------
if (legacyMode === false || (legacyMode !== true && SMWindow.LegacyMode !== true)) // Use jQuery dialog unless Legacy Mode is enabled for this window or globally in config.xml.php
{
// Load jQuery
SMResourceManager.Jquery.LoadInternal(function($)
{
// Create dialog
var dialog = null;
var iframe = null;
dialog = $("").html("").dialog(
{
dialogClass: "Sitemagic SMWindow", /* jQuery UI theme uses the Sitemagic class as a scope for styling */
autoOpen: false,
modal: (me.preferModal === true),
title: "",
width: me.width,
height: me.height + 28, // Add 28px which is the height of the title panel (not very flexibile - might be changed by custom CSS!)
position: ((me.centerWindow === false) ? [me.positionLeft, me.positionTop] : null), // null = centered
resizable: me.resizable,
closeOnEscape: false,
open: function()
{
if (me.showCallback !== null)
fireEvent("OnShow", me.showCallback);
},
close: function(event, ui)
{
if (me.closeCallback !== null)
fireEvent("OnClose", me.closeCallback);
me.instance = null;
me.dialog = null;
dialog.dialog("destroy").remove();
},
resizeStart: function(ev, ui) { $(ev.target.parentNode).addClass("SMWindowTransparent"); },
resizeStop: function(ev, ui) { $(ev.target.parentNode).removeClass("SMWindowTransparent"); },
dragStart: function(ev, ui) { $(ev.target.parentNode).addClass("SMWindowTransparent"); },
dragStop: function(ev, ui) { $(ev.target.parentNode).removeClass("SMWindowTransparent"); }
});
iframe = dialog[0].firstChild;
me.instance = iframe.contentWindow;
// Register OnLoad handler
SMEventHandler.AddEventHandler(iframe, "load", function()
{
if (me.loadCallback !== null && me.loadCallbackFired === false)
{
me.loadCallbackFired = true; // Only fire first time a page is loaded, to mimic behaviour of browser pop up (not fired when navigating)
fireEvent("OnLoad", me.loadCallback);
}
});
// Load content
if (me.content !== "")
{
iframe.contentWindow.document.write(me.content);
iframe.contentWindow.document.close();
}
else
{
iframe.src = me.url;
}
// Open dialog
dialog.dialog("open");
me.dialog = dialog; // Make dialog available to SMWindow.Close()
});
return;
}
// ---------------------------------------
// LEGACY MODE - Old browser pop up window
// ---------------------------------------
if (this.centerWindow === true)
{
this.positionLeft = Math.floor((screen.width / 2) - (this.width / 2));
this.positionTop = Math.floor((screen.height / 2) - (this.height / 2));
}
var options = "width=" + this.width + ",height=" + this.height;
options += ",toolbar=" + ((this.displayToolBar === true) ? "yes" : "no");
options += ",location=" + ((this.displayLocation === true) ? "yes" : "no");
options += ",menubar=" + ((this.displayMenuBar === true) ? "yes" : "no");
options += ",status=" + ((this.displayStatusBar === true) ? "yes" : "no");
options += ",scrollbars=" + ((this.displayScrollBars === true) ? "yes" : "no");
options += ",resizable=" + ((this.resizable === true) ? "yes" : "no");
options += ",left=" + this.positionLeft;
options += ",top=" + this.positionTop;
this.instance = window.open(((this.content === "") ? this.url : ""), this.id, options);
if (this.instance === null || this.instance === undefined)
{
alert("Your browser prevented this website from opening a window - please enable pop up windows");
this.instance = null;
return;
}
if (this.content !== "")
{
this.instance.document.write(this.content);
this.instance.document.close();
}
// Event handlers
// Notice:
// OnLoad is a bit tricky. For most browsers it fires, but not always for earlier versions of IE.
// If static content is set, it doesn't fire, unless it contains references to external resources
// such as images. That causes OnLoad to be triggered.
// Therefore OnLoad is registered but also triggered manually in case the browser doesn't trigger it.
// A simple boolean flag ensure that the event is only fired once.
// It seems reasonable to let the OnLoad event fire immediately (when done manually) since the
// document content has already been set above and the document instance closed.
try // Browser might throw Access Denied error for foreign domains (e.g. Safari and IE), while other browsers just ignore this (e.g. Chrome)
{
SMEventHandler.AddEventHandler(this.instance, "load", function()
{
if (me.loadCallback !== null && me.loadCallbackFired === false)
{
me.loadCallbackFired = true;
fireEvent("OnLoad", me.loadCallback);
}
});
}
catch (err)
{
if (window.console)
{
console.log(err.message);
console.log(err.stack);
console.log(err);
if (this.loadCallback !== null)
console.log("Unable to register OnLoad event handler - access denied - most likely due to Same-Origin Policy");
}
}
// OnLoad not always fired when static content is set (see explaination above) - make sure it fires by doing so manually
if (this.content !== "" && this.loadCallback !== null && this.loadCallbackFired === false)
{
this.loadCallbackFired = true;
fireEvent("OnLoad", this.loadCallback);
}
// Using Interval to support OnClose event - OnBeforeUnload only works if page is loaded from the same domain (Same-Origin Policy)
var iId = null;
iId = setInterval(function()
{
if (me.instance.closed === true) /*me.instance === null*/
{
// Known limitation in JS: Callback handlers set from within dialog window will not work in Legacy Mode. Example:
// (window.opener || window.top).SMWindow.GetInstance(window.name).SetOnCloseCallback(function() { /* ... */ });
// Callback must be defined in window which created dialog window. This is most likely due to the
// fact that the callback's execution context (scope/closure) is destroyed once the window no longer exists,
// which prevents the callback from working.
// Registering the callback on the parent window is not sufficient either since the execution context still belongs
// to the dialog window. Therefore the example below won't work:
// var parentWin = (window.opener || window.top);
// parentWin.MyCallback = function() { /* ... */ };
// parentWin.SMWindow.GetInstance(window.name).SetOnCloseCallback(parentWin.MyCallback);
// BUT, it IS possible to have the parent window register a callback defined by the dialog window by using the
// eval() function of the parent window (at least if the two pages are hosted on the same domain (Same-Origin)).
// Example:
// var parentWin = (window.opener || window.top);
// parentWin.eval("SMWindow.GetInstance('" + window.name + "').SetOnCloseCallback(function() { location.href = location.href; });");
// It's a bit messy, but the best we can do to work around this limitation in JavaScript. The problem is not related to SMWindow.
// If the solution above is not acceptable, avoid using Legacy Mode - use Dialog Mode instead!
if (me.closeCallback !== null)
fireEvent("OnClose", me.closeCallback);
me.instance = null;
clearInterval(iId);
}
}, 200);
if (this.showCallback !== null)
fireEvent("OnShow", this.showCallback);
}
///
///
/// Close window/dialog.
/// This is also possible from within the window using the following approach:
/// (window.opener || window.top).SMWindow.GetInstance(window.name).Close();
///
///
this.Close = function()
{
// NOTICE: Do not pass Close function directly as callback to e.g. setTimeout as it changes 'this'
// to the browser window instance (Legacy Mode) or the iFrame content window instance (Dialog Mode).
// Instead wrap it in an anonymous function like so: setTimeout(function() { smwin.Close(); }, 2000);
if (this.instance === null)
return;
if (this.dialog !== null)
{
this.dialog.dialog("close");
}
else
{
this.instance.close();
}
}
///
///
/// Returns internal browser window instance if open, otherwise Null.
/// The instance provides access to e.g. the window document:
/// var doc = smwin.GetInstance().document;
/// Notice: This is only possible with pages loaded from the same domain (Same-Origin Policy).
/// Also be aware that manipulating the document instance should not take place until during
/// or after OnLoad (see SetOnLoadCallback(..)).
///
///
this.GetInstance = function()
{
return this.instance; // Do NOT do console.log(smwin.GetInstance())! It sometimes crashes Chrome when debugging
}
function fireEvent(eventName, cb) // Fires event with error handling
{
try
{
cb();
}
catch (err)
{
if (window.console)
{
console.log("Error occurred executing " + eventName + " event handler");
console.log(err.message);
console.log(err.stack);
console.log(err);
}
}
}
}
SMWindow.instances = {};
///
/// Returns SMWindow instance by ID if found, otherwise Null
/// SMWindow instance ID
///
SMWindow.GetInstance = function(id)
{
return ((SMWindow.instances[id] !== undefined) ? SMWindow.instances[id] : null);
}
// SMHttpRequest
///
/// Asynchronous HTTP request functionality (AJAX).
///
/// // Example code
///
/// var http = new SMHttpRequest("CreateUser.php", true);
///
/// http.SetData("username=Jack&password=Secret");
/// http.SetStateListener(function()
/// {
/// if (this.GetCurrentState() === 4 && this.GetHttpStatus() === 200)
/// alert("User created - server said: " + this.GetResponseText());
/// });
///
/// http.Start();
///
///
/// Constructor - creates instance of SMHttpRequest
/// URL to request
/// Value indicating whether to perform request asynchronous or not
///
function SMHttpRequest(url, async) // url, true|false
{
this.url = url;
this.async = async;
this.httpRequest = getHttpRequestObject();
this.customHeaders = {};
this.data = null;
///
///
/// Add header to request.
/// Manually adding headers will prevent the SMHttpRequest instance from
/// manipulating headers. This is done to provide full control with the headers.
/// You will in this case most likely need to add the following header for a POST request:
/// Content-type : application/x-www-form-urlencoded
///
/// Header key
/// Header value
///
this.AddHeader = function(key, value)
{
this.customHeaders[key] = value;
}
///
/// Set data to post - this will change the request method from GET to POST
/// Data to send
///
this.SetData = function(data)
{
this.data = data;
}
///
/// Invoke request
///
this.Start = function()
{
var method = ((this.data === null || this.data === "") ? "GET" : "POST");
this.httpRequest.open(method, this.url, this.async);
var usingCustomHeaders = false;
for (var header in this.customHeaders)
{
this.httpRequest.setRequestHeader(header, this.customHeaders[header]);
usingCustomHeaders = true;
}
if (method === "POST" && usingCustomHeaders === false)
this.httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
this.httpRequest.send(this.data);
}
///
///
/// Returns result from request as XML or HTML document.
/// Return value will only be as expected if GetCurrentState() returns a value of 4
/// (request done) and GetHttpStatus() returns a value of 200 (request successful).
///
///
this.GetResponseXml = function()
{
return this.httpRequest.responseXML;
}
///
///
/// Returns text result from request.
/// Return value will only be as expected if GetCurrentState() returns a value of 4
/// (request done) and GetHttpStatus() returns a value of 200 (request successful).
///
///
this.GetResponseText = function()
{
return this.httpRequest.responseText;
}
///
///
/// Returns result from request as JSON object, Null if no response was returned.
/// Return value will only be as expected if GetCurrentState() returns a value of 4
/// (request done) and GetHttpStatus() returns a value of 200 (request successful).
///
///
this.GetResponseJson = function()
{
return ((this.httpRequest.responseText !== "") ? JSON.parse(this.httpRequest.responseText) : null);
}
///
///
/// Set delegate to invoke when request state is changed.
/// Use GetCurrentState() to read the state at the given time.
///
/// JavaScript function invoked when state changes
///
this.SetStateListener = function(func)
{
this.httpRequest.onreadystatechange = func;
}
///
///
/// Get current request state.
/// 0 = Unsent
/// 1 = Opened
/// 2 = Headers received
/// 3 = Loading
/// 4 = Done (response is ready for processing)
///
///
this.GetCurrentState = function() // 0 = unsent, 1 = opened, 2 = headers received, 3 = loading, 4 = done
{
return this.httpRequest.readyState;
}
///
///
/// Returns HTTP status. Common return values are:
/// 200 = OK (successful request)
/// 304 = Forbidden (access denied)
/// 404 = Not found
/// 408 = Request time out
/// 500 = Internal server error
/// 503 = Service unavailable
///
///
this.GetHttpStatus = function()
{
return this.httpRequest.status;
}
function getHttpRequestObject()
{
if (window.XMLHttpRequest) // Firefox, IE7, Chrome, Opera, Safari
return new XMLHttpRequest();
else if (window.ActiveXObject) // IE5, IE6
return new ActiveXObject("Microsoft.XMLHTTP");
else
{
//alert("Http Request object not supported");
return null;
}
}
}
// SMBrowser
///
/// Provides access to various browser information.
///
/// // Example code
///
/// var browserName = SMBrowser.GetBrowser();
/// var browserVersion = SMBrowser.GetVersion();
/// var browserLanguage = SMBrowser.GetLanguage();
///
/// if (browserName === "MSIE" && browserVersion < 7)
/// {
/// if (browserLanguage === "da")
/// alert("Opgrader venligst til IE7 eller nyere");
/// else
/// alert("Please upgrade to IE7 or newer");
/// }
///
function SMBrowser()
{
}
///
/// Returns browser name. Possible values are: Chrome, Safari, MSIE, Firefox, Opera, Unknown
///
SMBrowser.GetBrowser = function()
{
var agent = navigator.userAgent;
if (agent.indexOf("Chrome") > -1)
return "Chrome";
if (agent.indexOf("Safari") > -1)
return "Safari";
if (agent.indexOf("MSIE") > -1 || agent.indexOf("Trident") > -1)
return "MSIE";
if (agent.indexOf("Firefox") > -1)
return "Firefox";
if (agent.indexOf("Opera") > -1)
return "Opera";
return "Unknown";
}
///
/// Returns major version number for known browsers, -1 for unknown browsers
///
SMBrowser.GetVersion = function()
{
var start = 0;
var end = 0;
var agent = navigator.userAgent;
if (SMBrowser.GetBrowser() === "Chrome")
{
start = agent.indexOf("Chrome/");
start = (start !== -1 ? start + 7 : 0);
end = agent.indexOf(".", start);
end = (end !== -1 ? end : 0);
}
if (SMBrowser.GetBrowser() === "Safari")
{
start = agent.indexOf("Version/");
start = (start !== -1 ? start + 8 : 0);
end = agent.indexOf(".", start);
end = (end !== -1 ? end : 0);
}
if (SMBrowser.GetBrowser() === "MSIE")
{
if (agent.indexOf("MSIE") > -1)
{
start = agent.indexOf("MSIE ");
start = (start !== -1 ? start + 5 : 0);
end = agent.indexOf(".", start);
end = (end !== -1 ? end : 0);
}
else if (agent.indexOf("Trident") > -1) // IE11+
{
start = agent.indexOf("rv:");
start = (start !== -1 ? start + 3 : 0);
end = agent.indexOf(".", start);
end = (end !== -1 ? end : 0);
}
}
if (SMBrowser.GetBrowser() === "Firefox")
{
start = agent.indexOf("Firefox/");
start = (start !== -1 ? start + 8 : 0);
end = agent.indexOf(".", start);
end = (end !== -1 ? end : 0);
}
if (SMBrowser.GetBrowser() === "Opera")
{
start = agent.indexOf("Version/");
start = (start !== -1 ? start + 8 : -1);
if (start === -1)
{
start = agent.indexOf("Opera/");
start = (start !== -1 ? start + 6 : -1);
}
if (start === -1)
{
start = agent.indexOf("Opera ");
start = (start !== -1 ? start + 6 : -1);
}
end = agent.indexOf(".", start);
end = (end !== -1 ? end : 0);
}
if (start !== 0 && start !== 0)
return parseInt(agent.substring(start, end));
return -1;
}
///
/// Returns browser language - e.g. "da" (Danish), "en" (English) etc.
///
SMBrowser.GetLanguage = function()
{
var lang = null;
if (navigator.language)
lang = navigator.language.toLowerCase();
else if (navigator.browserLanguage)
lang = navigator.browserLanguage.toLowerCase();
if (lang === null || lang === "")
return "en";
if (lang.length === 2)
return lang;
if (lang.length === 5)
return lang.substring(0, 2);
return "en";
}
///
/// Returns page width in pixels on succes, -1 on failure
///
SMBrowser.GetPageWidth = function()
{
var w = -1;
if (window.innerWidth) // W3C
w = window.innerWidth;
else if (document.documentElement && document.documentElement.clientWidth) // IE 6-8 (not quirks mode)
w = document.documentElement.clientWidth;
return w;
}
///
/// Returns page height in pixels on succes, -1 on failure
///
SMBrowser.GetPageHeight = function()
{
var h = -1;
if (window.innerHeight) // W3C
h = window.innerHeight;
else if (document.documentElement && document.documentElement.clientHeight) // IE 6-8 (not quirks mode)
h = document.documentElement.clientHeight;
return h;
}
///
/// Get screen width
/// Set True to return only available space
///
SMBrowser.GetScreenWidth = function(onlyAvailable)
{
if (onlyAvailable === true)
return window.screen.availWidth;
return window.screen.width;
}
///
/// Get screen height
/// Set True to return only available space
///
SMBrowser.GetScreenHeight = function(onlyAvailable)
{
if (onlyAvailable === true)
return window.screen.availHeight;
return window.screen.height;
}
///
///
/// Check whether specified CSS property and CSS value is supported by browser.
/// Returns True if supported, otherwise False.
///
/// CSS property name
/// CSS property value
///
SMBrowser.CssSupported = function(property, value)
{
var div = document.createElement("div");
var cssText = div.style.cssText;
try
{
div.style[property] = value;
}
catch (err)
{
return false;
}
return (cssText !== div.style.cssText);
}
// SMResourceManager
// The order of processing scripts and stylesheets:
// http://www.html5rocks.com/en/tutorials/internals/howbrowserswork/#The_order_of_processing_scripts_and_style_sheets
///
/// The Resource Manager is a useful mechanism for loading styleheets and JavaScript on demand in a non blocking manner.
///
function SMResourceManager()
{
}
SMResourceManager.Jquery = {};
SMResourceManager.Jquery.Loading = false;
SMResourceManager.Jquery.Loaded = {};
SMResourceManager.Jquery.Internal = null;
///
///
/// Load client script on demand in a non-blocking manner.
///
/// // Example of loading a JavaScript file
///
/// SMResourceManager.LoadScript("extensions/test/test.js", function(src)
/// {
/// alert("JavaScript " + src + " loaded and ready to be used!");
/// });
///
/// Script source (path or URL)
///
/// Callback function fired when script loading is complete - takes the script source requested as an argument.
/// Be aware that a load error will also trigger the callback to make sure control is always returned.
/// Consider using feature detection within callback function for super reliable execution - example:
/// if (expectedObjectOrFunction) { /* Successfully loaded, continue.. */ }
///
///
SMResourceManager.LoadScript = function(src, callback)
{
var script = document.createElement("script");
script.type = "text/javascript";
if (callback !== undefined && (SMBrowser.GetBrowser() !== "MSIE" || (SMBrowser.GetBrowser() === "MSIE" && SMBrowser.GetVersion() >= 9)))
{
script.onload = function() { callback(src); };
// Terrible, but we need same behaviour for all browsers, and IE8 (and below) does not distinguish between success and failure.
// Also, we need to make sure control is returned no matter what - just like using ordinary