// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information.
define([
'exports',
'../Core/_Global',
'../Core/_Base',
'../Core/_BaseUtils',
'../Core/_WinRT',
'../Promise',
'../Scheduler'
], function elementUtilities(exports, _Global, _Base, _BaseUtils, _WinRT, Promise, Scheduler) {
"use strict";
// not supported in WebWorker
if (!_Global.document) {
return;
}
var _zoomToDuration = 167;
// Firefox's implementation of getComputedStyle returns null when called within
// an iframe that is display:none. This is a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=548397
// _getComputedStyle is a helper which is guaranteed to return an object whose keys
// map to strings.
var defaultComputedStyle = null;
function getDefaultComputedStyle() {
if (!defaultComputedStyle) {
defaultComputedStyle = {};
Object.keys(_Global.CSS2Properties.prototype).forEach(function (cssProperty) {
defaultComputedStyle[cssProperty] = "";
});
}
return defaultComputedStyle;
}
function _getComputedStyle(element, pseudoElement) {
// jscs:disable disallowDirectGetComputedStyle
return _Global.getComputedStyle(element, pseudoElement) || getDefaultComputedStyle();
// jscs:enable disallowDirectGetComputedStyle
}
function removeEmpties(arr) {
var len = arr.length;
for (var i = len - 1; i >= 0; i--) {
if (!arr[i]) {
arr.splice(i, 1);
len--;
}
}
return len;
}
function getClassName(e) {
var name = e.className || "";
if (typeof (name) === "string") {
return name;
} else {
return name.baseVal || "";
}
}
function setClassName(e, value) {
// SVG elements (which use e.className.baseVal) are never undefined,
// so this logic makes the comparison a bit more compact.
//
var name = e.className || "";
if (typeof (name) === "string") {
e.className = value;
} else {
e.className.baseVal = value;
}
return e;
}
function addClass(e, name) {
///
///
/// Adds the specified class(es) to the specified element. Multiple classes can be added using space delimited names.
///
///
/// The element to which to add the class.
///
///
/// The name of the class to add, multiple classes can be added using space delimited names
///
///
/// The element.
///
///
if (e.classList) {
// Fastpath: adding a single class, no need to string split the argument
if (name.indexOf(" ") < 0) {
e.classList.add(name);
} else {
var namesToAdd = name.split(" ");
removeEmpties(namesToAdd);
for (var i = 0, len = namesToAdd.length; i < len; i++) {
e.classList.add(namesToAdd[i]);
}
}
return e;
} else {
var className = getClassName(e);
var names = className.split(" ");
var l = removeEmpties(names);
var toAdd;
// we have a fast path for the common case of a single name in the class name
//
if (name.indexOf(" ") >= 0) {
var namesToAdd = name.split(" ");
removeEmpties(namesToAdd);
for (var i = 0; i < l; i++) {
var found = namesToAdd.indexOf(names[i]);
if (found >= 0) {
namesToAdd.splice(found, 1);
}
}
if (namesToAdd.length > 0) {
toAdd = namesToAdd.join(" ");
}
} else {
var saw = false;
for (var i = 0; i < l; i++) {
if (names[i] === name) {
saw = true;
break;
}
}
if (!saw) { toAdd = name; }
}
if (toAdd) {
if (l > 0 && names[0].length > 0) {
setClassName(e, className + " " + toAdd);
} else {
setClassName(e, toAdd);
}
}
return e;
}
}
function removeClass(e, name) {
///
///
/// Removes the specified class from the specified element.
///
///
/// The element from which to remove the class.
///
///
/// The name of the class to remove.
///
///
/// The element.
///
///
if (e.classList) {
// Fastpath: Nothing to remove
if (e.classList.length === 0) {
return e;
}
var namesToRemove = name.split(" ");
removeEmpties(namesToRemove);
for (var i = 0, len = namesToRemove.length; i < len; i++) {
e.classList.remove(namesToRemove[i]);
}
return e;
} else {
var original = getClassName(e);
var namesToRemove;
var namesToRemoveLen;
if (name.indexOf(" ") >= 0) {
namesToRemove = name.split(" ");
namesToRemoveLen = removeEmpties(namesToRemove);
} else {
// early out for the case where you ask to remove a single
// name and that name isn't found.
//
if (original.indexOf(name) < 0) {
return e;
}
namesToRemove = [name];
namesToRemoveLen = 1;
}
var removed;
var names = original.split(" ");
var namesLen = removeEmpties(names);
for (var i = namesLen - 1; i >= 0; i--) {
if (namesToRemove.indexOf(names[i]) >= 0) {
names.splice(i, 1);
removed = true;
}
}
if (removed) {
setClassName(e, names.join(" "));
}
return e;
}
}
function toggleClass(e, name) {
///
///
/// Toggles (adds or removes) the specified class on the specified element.
/// If the class is present, it is removed; if it is absent, it is added.
///
///
/// The element on which to toggle the class.
///
///
/// The name of the class to toggle.
///
///
/// The element.
///
///
if (e.classList) {
e.classList.toggle(name);
return e;
} else {
var className = getClassName(e);
var names = className.trim().split(" ");
var l = names.length;
var found = false;
for (var i = 0; i < l; i++) {
if (names[i] === name) {
found = true;
}
}
if (!found) {
if (l > 0 && names[0].length > 0) {
setClassName(e, className + " " + name);
} else {
setClassName(e, className + name);
}
} else {
setClassName(e, names.reduce(function (r, e) {
if (e === name) {
return r;
} else if (r && r.length > 0) {
return r + " " + e;
} else {
return e;
}
}, ""));
}
return e;
}
}
// Only set the attribute if its value has changed
function setAttribute(element, attribute, value) {
if (element.getAttribute(attribute) !== "" + value) {
element.setAttribute(attribute, value);
}
}
function _clamp(value, lowerBound, upperBound, defaultValue) {
var n = Math.max(lowerBound, Math.min(upperBound, +value));
return n === 0 ? 0 : n || Math.max(lowerBound, Math.min(upperBound, defaultValue));
}
var _pixelsRE = /^-?\d+\.?\d*(px)?$/i;
var _numberRE = /^-?\d+/i;
function convertToPixels(element, value) {
///
///
/// Converts a CSS positioning string for the specified element to pixels.
///
///
/// The element.
///
///
/// The CSS positioning string.
///
///
/// The number of pixels.
///
///
if (!_pixelsRE.test(value) && _numberRE.test(value)) {
var previousValue = element.style.left;
element.style.left = value;
value = element.style.pixelLeft;
element.style.left = previousValue;
return value;
} else {
return Math.round(parseFloat(value)) || 0;
}
}
function getDimension(element, property) {
return convertToPixels(element, _getComputedStyle(element, null)[property]);
}
function _convertToPrecisePixels(value) {
return parseFloat(value) || 0;
}
function _getPreciseDimension(element, property) {
return _convertToPrecisePixels(_getComputedStyle(element, null)[property]);
}
function _getPreciseMargins(element) {
var style = _getComputedStyle(element);
return {
top: _convertToPrecisePixels(style.marginTop),
right: _convertToPrecisePixels(style.marginRight),
bottom: _convertToPrecisePixels(style.marginBottom),
left: _convertToPrecisePixels(style.marginLeft),
};
}
var _MSGestureEvent = _Global.MSGestureEvent || {
MSGESTURE_FLAG_BEGIN: 1,
MSGESTURE_FLAG_CANCEL: 4,
MSGESTURE_FLAG_END: 2,
MSGESTURE_FLAG_INERTIA: 8,
MSGESTURE_FLAG_NONE: 0
};
var _MSManipulationEvent = _Global.MSManipulationEvent || {
MS_MANIPULATION_STATE_ACTIVE: 1,
MS_MANIPULATION_STATE_CANCELLED: 6,
MS_MANIPULATION_STATE_COMMITTED: 7,
MS_MANIPULATION_STATE_DRAGGING: 5,
MS_MANIPULATION_STATE_INERTIA: 2,
MS_MANIPULATION_STATE_PRESELECT: 3,
MS_MANIPULATION_STATE_SELECTING: 4,
MS_MANIPULATION_STATE_STOPPED: 0
};
var _MSPointerEvent = _Global.MSPointerEvent || {
MSPOINTER_TYPE_TOUCH: "touch",
MSPOINTER_TYPE_PEN: "pen",
MSPOINTER_TYPE_MOUSE: "mouse",
};
// Helpers for managing element._eventsMap for custom events
//
function addListenerToEventMap(element, type, listener, useCapture, data) {
var eventNameLowercase = type.toLowerCase();
if (!element._eventsMap) {
element._eventsMap = {};
}
if (!element._eventsMap[eventNameLowercase]) {
element._eventsMap[eventNameLowercase] = [];
}
element._eventsMap[eventNameLowercase].push({
listener: listener,
useCapture: useCapture,
data: data
});
}
function removeListenerFromEventMap(element, type, listener, useCapture) {
var eventNameLowercase = type.toLowerCase();
var mappedEvents = element._eventsMap && element._eventsMap[eventNameLowercase];
if (mappedEvents) {
for (var i = mappedEvents.length - 1; i >= 0; i--) {
var mapping = mappedEvents[i];
if (mapping.listener === listener && (!!useCapture === !!mapping.useCapture)) {
mappedEvents.splice(i, 1);
return mapping;
}
}
}
return null;
}
function lookupListeners(element, type) {
var eventNameLowercase = type.toLowerCase();
return element._eventsMap && element._eventsMap[eventNameLowercase] && element._eventsMap[eventNameLowercase].slice(0) || [];
}
// Custom focusin/focusout events
// Generally, use these instead of using the browser's blur/focus/focusout/focusin events directly.
// However, this doesn't support the window object. If you need to listen to focus events on the window,
// use the browser's events directly.
//
// In order to send our custom focusin/focusout events synchronously on every browser, we feature detect
// for native "focusin" and "focusout" since every browser that supports them will fire them synchronously.
// Every browser in our support matrix, except for IE, also fires focus/blur synchronously, we fall back to
// those events in browsers such as Firefox that do not have native support for focusin/focusout.
function bubbleEvent(element, type, eventObject) {
while (element) {
var handlers = lookupListeners(element, type);
for (var i = 0, len = handlers.length; i < len; i++) {
handlers[i].listener.call(element, eventObject);
}
element = element.parentNode;
}
}
function prepareFocusEvent(eventObject) {
// If an iframe is involved, then relatedTarget should be null.
if (eventObject.relatedTarget && eventObject.relatedTarget.tagName === "IFRAME" ||
eventObject.target && eventObject.target.tagName === "IFRAME") {
eventObject.relatedTarget = null;
}
return eventObject;
}
var nativeSupportForFocusIn = "onfocusin" in _Global.document.documentElement;
var activeElement = null;
_Global.addEventListener(nativeSupportForFocusIn ? "focusout" : "blur", function (eventObject) {
// Fires focusout when focus move to another window or into an iframe.
if (eventObject.target === _Global) {
var previousActiveElement = activeElement;
if (previousActiveElement) {
bubbleEvent(previousActiveElement, "focusout", prepareFocusEvent({
type: "focusout",
target: previousActiveElement,
relatedTarget: null
}));
}
activeElement = null;
}
});
_Global.document.documentElement.addEventListener(nativeSupportForFocusIn ? "focusin" : "focus", function (eventObject) {
var previousActiveElement = activeElement;
activeElement = eventObject.target;
if (previousActiveElement) {
bubbleEvent(previousActiveElement, "focusout", prepareFocusEvent({
type: "focusout",
target: previousActiveElement,
relatedTarget: activeElement
}));
}
if (activeElement) {
bubbleEvent(activeElement, "focusin", prepareFocusEvent({
type: "focusin",
target: activeElement,
relatedTarget: previousActiveElement
}));
}
}, true);
function registerBubbleListener(element, type, listener, useCapture) {
if (useCapture) {
throw "This custom WinJS event only supports bubbling";
}
addListenerToEventMap(element, type, listener, useCapture);
}
// Custom pointer events
//
// Sets the properties in *overrideProperties* on the object. Delegates all other
// property accesses to *eventObject*.
//
// The purpose of PointerEventProxy is that it allows us to customize properties on
// an eventObject despite those properties being unwritable and unconfigurable.
var PointerEventProxy = function (eventObject, overrideProperties) {
overrideProperties = overrideProperties || {};
this.__eventObject = eventObject;
var that = this;
Object.keys(overrideProperties).forEach(function (propertyName) {
Object.defineProperty(that, propertyName, {
value: overrideProperties[propertyName]
});
});
};
// Define PointerEventProxy properties which should be delegated to the original eventObject.
[
"altKey", "AT_TARGET", "bubbles", "BUBBLING_PHASE", "button", "buttons",
"cancelable", "cancelBubble", "CAPTURING_PHASE", "clientX", "clientY",
"ctrlKey", "currentTarget", "defaultPrevented", "detail", "eventPhase",
"fromElement", "getModifierState", "height", "hwTimestamp", "initEvent",
"initMouseEvent", "initPointerEvent", "initUIEvent", "isPrimary", "isTrusted",
"layerX", "layerY", "metaKey", "offsetX", "offsetY", "pageX", "pageY",
"pointerId", "pointerType", "pressure", "preventDefault", "relatedTarget",
"rotation", "screenX", "screenY", "shiftKey", "srcElement", "stopImmediatePropagation",
"stopPropagation", "target", "tiltX", "tiltY", "timeStamp", "toElement", "type",
"view", "which", "width", "x", "y", "_normalizedType", "_fakedBySemanticZoom"
].forEach(function (propertyName) {
Object.defineProperty(PointerEventProxy.prototype, propertyName, {
get: function () {
var value = this.__eventObject[propertyName];
return typeof value === "function" ? value.bind(this.__eventObject) : value;
},
configurable: true
});
});
function touchEventTranslator(callback, eventObject) {
var changedTouches = eventObject.changedTouches,
retVal = null;
if (!changedTouches) {
return retVal;
}
for (var i = 0, len = changedTouches.length; i < len; i++) {
var touchObject = changedTouches[i];
var pointerEventObject = new PointerEventProxy(eventObject, {
pointerType: _MSPointerEvent.MSPOINTER_TYPE_TOUCH,
pointerId: touchObject.identifier,
isPrimary: i === 0,
screenX: touchObject.screenX,
screenY: touchObject.screenY,
clientX: touchObject.clientX,
clientY: touchObject.clientY,
pageX: touchObject.pageX,
pageY: touchObject.pageY,
radiusX: touchObject.radiusX,
radiusY: touchObject.radiusY,
rotationAngle: touchObject.rotationAngle,
force: touchObject.force,
_currentTouch: touchObject
});
var newRetVal = callback(pointerEventObject);
retVal = retVal || newRetVal;
}
return retVal;
}
function mouseEventTranslator(callback, eventObject) {
eventObject.pointerType = _MSPointerEvent.MSPOINTER_TYPE_MOUSE;
eventObject.pointerId = -1;
eventObject.isPrimary = true;
return callback(eventObject);
}
function mspointerEventTranslator(callback, eventObject) {
return callback(eventObject);
}
var eventTranslations = {
pointerdown: {
touch: "touchstart",
mspointer: "MSPointerDown",
mouse: "mousedown"
},
pointerup: {
touch: "touchend",
mspointer: "MSPointerUp",
mouse: "mouseup"
},
pointermove: {
touch: "touchmove",
mspointer: "MSPointerMove",
mouse: "mousemove"
},
pointerenter: {
touch: "touchenter",
mspointer: "MSPointerEnter",
mouse: "mouseenter"
},
pointerover: {
touch: null,
mspointer: "MSPointerOver",
mouse: "mouseover"
},
pointerout: {
touch: "touchleave",
mspointer: "MSPointerOut",
mouse: "mouseout"
},
pointercancel: {
touch: "touchcancel",
mspointer: "MSPointerCancel",
mouse: null
}
};
function registerPointerEvent(element, type, callback, capture) {
var eventNameLowercase = type.toLowerCase();
var mouseWrapper,
touchWrapper,
mspointerWrapper;
var translations = eventTranslations[eventNameLowercase];
// Browsers fire a touch event and then a mouse event when the input is touch. touchHandled is used to prevent invoking the pointer callback twice.
var touchHandled;
// If we are in IE10, we should use MSPointer as it provides a better interface than touch events
if (_Global.MSPointerEvent) {
mspointerWrapper = function (eventObject) {
eventObject._normalizedType = eventNameLowercase;
touchHandled = true;
return mspointerEventTranslator(callback, eventObject);
};
element.addEventListener(translations.mspointer, mspointerWrapper, capture);
} else {
// Otherwise, use a mouse and touch event
if (translations.mouse) {
mouseWrapper = function (eventObject) {
eventObject._normalizedType = eventNameLowercase;
if (!touchHandled) {
return mouseEventTranslator(callback, eventObject);
}
touchHandled = false;
};
element.addEventListener(translations.mouse, mouseWrapper, capture);
}
if (translations.touch) {
touchWrapper = function (eventObject) {
eventObject._normalizedType = eventNameLowercase;
touchHandled = true;
return touchEventTranslator(callback, eventObject);
};
element.addEventListener(translations.touch, touchWrapper, capture);
}
}
addListenerToEventMap(element, type, callback, capture, {
mouseWrapper: mouseWrapper,
touchWrapper: touchWrapper,
mspointerWrapper: mspointerWrapper
});
}
function unregisterPointerEvent(element, type, callback, capture) {
var eventNameLowercase = type.toLowerCase();
var mapping = removeListenerFromEventMap(element, type, callback, capture);
if (mapping) {
var translations = eventTranslations[eventNameLowercase];
if (mapping.data.mouseWrapper) {
element.removeEventListener(translations.mouse, mapping.data.mouseWrapper, capture);
}
if (mapping.data.touchWrapper) {
element.removeEventListener(translations.touch, mapping.data.touchWrapper, capture);
}
if (mapping.data.mspointerWrapper) {
element.removeEventListener(translations.mspointer, mapping.data.mspointerWrapper, capture);
}
}
}
// Custom events dispatch table. Event names should be lowercased.
//
var customEvents = {
focusout: {
register: registerBubbleListener,
unregister: removeListenerFromEventMap
},
focusin: {
register: registerBubbleListener,
unregister: removeListenerFromEventMap
},
};
if (!_Global.PointerEvent) {
var pointerEventEntry = {
register: registerPointerEvent,
unregister: unregisterPointerEvent
};
customEvents.pointerdown = pointerEventEntry;
customEvents.pointerup = pointerEventEntry;
customEvents.pointermove = pointerEventEntry;
customEvents.pointerenter = pointerEventEntry;
customEvents.pointerover = pointerEventEntry;
customEvents.pointerout = pointerEventEntry;
customEvents.pointercancel = pointerEventEntry;
}
// The MutationObserverShim only supports the following configuration:
// attributes
// attributeFilter
var MutationObserverShim = _Base.Class.define(
function MutationObserverShim_ctor(callback) {
this._callback = callback;
this._toDispose = [];
this._attributeFilter = [];
this._scheduled = false;
this._pendingChanges = [];
this._observerCount = 0;
this._handleCallback = this._handleCallback.bind(this);
this._targetElements = [];
},
{
observe: function MutationObserverShim_observe(element, configuration) {
if (this._targetElements.indexOf(element) === -1) {
this._targetElements.push(element);
}
this._observerCount++;
if (configuration.attributes) {
this._addRemovableListener(element, "DOMAttrModified", this._handleCallback);
}
if (configuration.attributeFilter) {
this._attributeFilter = configuration.attributeFilter;
}
},
disconnect: function MutationObserverShim_disconnect() {
this._observerCount = 0;
this._targetElements = [];
this._toDispose.forEach(function (disposeFunc) {
disposeFunc();
});
},
_addRemovableListener: function MutationObserverShim_addRemovableListener(target, event, listener) {
target.addEventListener(event, listener);
this._toDispose.push(function () {
target.removeEventListener(event, listener);
});
},
_handleCallback: function MutationObserverShim_handleCallback(evt) {
// prevent multiple events from firing when nesting observers
evt.stopPropagation();
var attrName = evt.attrName;
if (this._attributeFilter.length && this._attributeFilter.indexOf(attrName) === -1) {
return;
}
// subtree:true is not currently supported
if (this._targetElements.indexOf(evt.target) === -1) {
return;
}
var isAriaMutation = attrName.indexOf("aria") >= 0;
// DOM mutation events use different naming for this attribute
if (attrName === 'tabindex') {
attrName = 'tabIndex';
}
this._pendingChanges.push({
type: 'attributes',
target: evt.target,
attributeName: attrName
});
if (this._observerCount === 1 && !isAriaMutation) {
this._dispatchEvent();
} else if (this._scheduled === false) {
this._scheduled = true;
_BaseUtils._setImmediate(this._dispatchEvent.bind(this));
}
},
_dispatchEvent: function MutationObserverShim_dispatchEvent() {
try {
this._callback(this._pendingChanges);
}
finally {
this._pendingChanges = [];
this._scheduled = false;
}
}
},
{
_isShim: true
}
);
var _MutationObserver = _Global.MutationObserver || MutationObserverShim;
// Lazily init singleton on first access.
var _resizeNotifier = null;
// Class to provide a global listener for window.onresize events.
// This keeps individual elements from having to listen to window.onresize
// and having to dispose themselves to avoid leaks.
var ResizeNotifier = _Base.Class.define(
function ElementResizer_ctor() {
_Global.addEventListener("resize", this._handleResize.bind(this));
},
{
subscribe: function ElementResizer_subscribe(element, handler) {
element.addEventListener(this._resizeEvent, handler);
addClass(element, this._resizeClass);
},
unsubscribe: function ElementResizer_unsubscribe(element, handler) {
removeClass(element, this._resizeClass);
element.removeEventListener(this._resizeEvent, handler);
},
_handleResize: function ElementResizer_handleResize() {
var resizables = _Global.document.querySelectorAll('.' + this._resizeClass);
var length = resizables.length;
for (var i = 0; i < length; i++) {
var event = _Global.document.createEvent("Event");
event.initEvent(this._resizeEvent, false, true);
resizables[i].dispatchEvent(event);
}
},
_resizeClass: { get: function () { return 'win-element-resize'; } },
_resizeEvent: { get: function () { return 'WinJSElementResize'; } }
}
);
// - object: The object on which GenericListener will listen for events.
// - objectName: A string representing the name of *object*. This will be
// incorporated into the names of the events and classNames created by
// GenericListener.
// - options
// - registerThruWinJSCustomEvents: If true, will register for events using
// _exports._addEventListener so that you can take advantage of WinJS's custom
// events (e.g. focusin, pointer*). Otherwise, registers directly on *object*
// using its add/removeEventListener methods.
var GenericListener = _Base.Class.define(
function GenericListener_ctor(objectName, object, options) {
options = options || {};
this.registerThruWinJSCustomEvents = !!options.registerThruWinJSCustomEvents;
this.objectName = objectName;
this.object = object;
this.capture = {};
this.bubble = {};
},
{
addEventListener: function GenericListener_addEventListener(element, name, listener, capture) {
name = name.toLowerCase();
var handlers = this._getHandlers(capture);
var handler = handlers[name];
if (!handler) {
handler = this._getListener(name, capture);
handler.refCount = 0;
handlers[name] = handler;
if (this.registerThruWinJSCustomEvents) {
exports._addEventListener(this.object, name, handler, capture);
} else {
this.object.addEventListener(name, handler, capture);
}
}
handler.refCount++;
element.addEventListener(this._getEventName(name, capture), listener);
addClass(element, this._getClassName(name, capture));
},
removeEventListener: function GenericListener_removeEventListener(element, name, listener, capture) {
name = name.toLowerCase();
var handlers = this._getHandlers(capture);
var handler = handlers[name];
if (handler) {
handler.refCount--;
if (handler.refCount === 0) {
if (this.registerThruWinJSCustomEvents) {
exports._removeEventListener(this.object, name, handler, capture);
} else {
this.object.removeEventListener(name, handler, capture);
}
delete handlers[name];
}
}
removeClass(element, this._getClassName(name, capture));
element.removeEventListener(this._getEventName(name, capture), listener);
},
_getHandlers: function GenericListener_getHandlers(capture) {
if (capture) {
return this.capture;
} else {
return this.bubble;
}
},
_getClassName: function GenericListener_getClassName(name, capture) {
var captureSuffix = capture ? 'capture' : 'bubble';
return 'win-' + this.objectName.toLowerCase() + '-event-' + name + captureSuffix;
},
_getEventName: function GenericListener_getEventName(name, capture) {
var captureSuffix = capture ? 'capture' : 'bubble';
return 'WinJS' + this.objectName + 'Event-' + name + captureSuffix;
},
_getListener: function GenericListener_getListener(name, capture) {
var listener = function GenericListener_generatedListener(ev) {
var targets = _Global.document.querySelectorAll('.' + this._getClassName(name, capture));
var length = targets.length;
var handled = false;
for (var i = 0; i < length; i++) {
var event = _Global.document.createEvent("Event");
event.initEvent(this._getEventName(name, capture), false, true);
event.detail = { originalEvent: ev };
var doDefault = targets[i].dispatchEvent(event);
handled = handled || !doDefault;
}
return handled;
};
return listener.bind(this);
}
}
);
var determinedRTLEnvironment = false,
usingWebkitScrollCoordinates = false,
usingFirefoxScrollCoordinates = false;
function determineRTLEnvironment() {
var element = _Global.document.createElement("div");
element.style.direction = "rtl";
element.innerHTML = "" +
"
";
_Global.document.body.appendChild(element);
var elementScroller = element.firstChild;
if (elementScroller.scrollLeft > 0) {
usingWebkitScrollCoordinates = true;
}
elementScroller.scrollLeft += 100;
if (elementScroller.scrollLeft === 0) {
usingFirefoxScrollCoordinates = true;
}
_Global.document.body.removeChild(element);
determinedRTLEnvironment = true;
}
function getAdjustedScrollPosition(element) {
var computedStyle = _getComputedStyle(element),
scrollLeft = element.scrollLeft;
if (computedStyle.direction === "rtl") {
if (!determinedRTLEnvironment) {
determineRTLEnvironment();
}
if (usingWebkitScrollCoordinates) {
scrollLeft = element.scrollWidth - element.clientWidth - scrollLeft;
}
scrollLeft = Math.abs(scrollLeft);
}
return {
scrollLeft: scrollLeft,
scrollTop: element.scrollTop
};
}
function setAdjustedScrollPosition(element, scrollLeft, scrollTop) {
if (scrollLeft !== undefined) {
var computedStyle = _getComputedStyle(element);
if (computedStyle.direction === "rtl") {
if (!determinedRTLEnvironment) {
determineRTLEnvironment();
}
if (usingFirefoxScrollCoordinates) {
scrollLeft = -scrollLeft;
} else if (usingWebkitScrollCoordinates) {
scrollLeft = element.scrollWidth - element.clientWidth - scrollLeft;
}
}
element.scrollLeft = scrollLeft;
}
if (scrollTop !== undefined) {
element.scrollTop = scrollTop;
}
}
function getScrollPosition(element) {
///
///
/// Gets the scrollLeft and scrollTop of the specified element, adjusting the scrollLeft to change from browser specific coordinates to logical coordinates when in RTL.
///
///
/// The element.
///
///
/// An object with two properties: scrollLeft and scrollTop
///
///
return getAdjustedScrollPosition(element);
}
function setScrollPosition(element, position) {
///
///
/// Sets the scrollLeft and scrollTop of the specified element, changing the scrollLeft from logical coordinates to browser-specific coordinates when in RTL.
///
///
/// The element.
///
///
/// The element.
///
///
position = position || {};
setAdjustedScrollPosition(element, position.scrollLeft, position.scrollTop);
}
// navigator.msManipulationViewsEnabled tells us whether snap points work or not regardless of whether the style properties exist, however,
// on Phone WWAs, this check returns false even though snap points are supported. To work around this bug, we check for the presence of
// 'MSAppHost' in the user agent string which indicates that we are in a WWA environment; all WWA environments support snap points.
var supportsSnapPoints = _Global.navigator.msManipulationViewsEnabled || _Global.navigator.userAgent.indexOf("MSAppHost") >= 0;
var supportsTouchDetection = !!(_Global.MSPointerEvent || _Global.TouchEvent);
var uniqueElementIDCounter = 0;
function uniqueID(e) {
if (!(e.uniqueID || e._uniqueID)) {
e._uniqueID = "element__" + (++uniqueElementIDCounter);
}
return e.uniqueID || e._uniqueID;
}
function ensureId(element) {
if (!element.id) {
element.id = uniqueID(element);
}
}
function _getCursorPos(eventObject) {
var docElement = _Global.document.documentElement;
var docScrollPos = getScrollPosition(docElement);
return {
left: eventObject.clientX + (_Global.document.body.dir === "rtl" ? -docScrollPos.scrollLeft : docScrollPos.scrollLeft),
top: eventObject.clientY + docElement.scrollTop
};
}
function _getElementsByClasses(parent, classes) {
var retVal = [];
for (var i = 0, len = classes.length; i < len; i++) {
var element = parent.querySelector("." + classes[i]);
if (element) {
retVal.push(element);
}
}
return retVal;
}
var _selectionPartsSelector = ".win-selectionborder, .win-selectionbackground, .win-selectioncheckmark, .win-selectioncheckmarkbackground";
var _dataKey = "_msDataKey";
_Base.Namespace._moduleDefine(exports, "WinJS.Utilities", {
_dataKey: _dataKey,
_supportsSnapPoints: {
get: function () {
return supportsSnapPoints;
}
},
_supportsTouchDetection: {
get: function () {
return supportsTouchDetection;
}
},
_uniqueID: uniqueID,
_ensureId: ensureId,
_clamp: _clamp,
_getCursorPos: _getCursorPos,
_getElementsByClasses: _getElementsByClasses,
_createGestureRecognizer: function () {
if (_Global.MSGesture) {
return new _Global.MSGesture();
}
var doNothing = function () {
};
return {
addEventListener: doNothing,
removeEventListener: doNothing,
addPointer: doNothing,
stop: doNothing
};
},
_MSGestureEvent: _MSGestureEvent,
_MSManipulationEvent: _MSManipulationEvent,
_elementsFromPoint: function (x, y) {
if (_Global.document.msElementsFromPoint) {
return _Global.document.msElementsFromPoint(x, y);
} else {
var element = _Global.document.elementFromPoint(x, y);
return element ? [element] : null;
}
},
_matchesSelector: function _matchesSelector(element, selectorString) {
var matchesSelector = element.matches
|| element.msMatchesSelector
|| element.mozMatchesSelector
|| element.webkitMatchesSelector;
return matchesSelector.call(element, selectorString);
},
_selectionPartsSelector: _selectionPartsSelector,
_isSelectionRendered: function _isSelectionRendered(itemBox) {
// The tree is changed at pointerDown but _selectedClass is added only when the user drags an item below the selection threshold so checking for _selectedClass is not reliable.
return itemBox.querySelectorAll(_selectionPartsSelector).length > 0;
},
_addEventListener: function _addEventListener(element, type, listener, useCapture) {
var eventNameLower = type && type.toLowerCase();
var entry = customEvents[eventNameLower];
var equivalentEvent = _BaseUtils._browserEventEquivalents[type];
if (entry) {
entry.register(element, type, listener, useCapture);
} else if (equivalentEvent) {
element.addEventListener(equivalentEvent, listener, useCapture);
} else {
element.addEventListener(type, listener, useCapture);
}
},
_removeEventListener: function _removeEventListener(element, type, listener, useCapture) {
var eventNameLower = type && type.toLowerCase();
var entry = customEvents[eventNameLower];
var equivalentEvent = _BaseUtils._browserEventEquivalents[type];
if (entry) {
entry.unregister(element, type, listener, useCapture);
} else if (equivalentEvent) {
element.removeEventListener(equivalentEvent, listener, useCapture);
} else {
element.removeEventListener(type, listener, useCapture);
}
},
_initEventImpl: function (initType, event, eventType) {
eventType = eventType.toLowerCase();
var mapping = eventTranslations[eventType];
if (mapping) {
switch (initType.toLowerCase()) {
case "pointer":
if (!_Global.PointerEvent) {
arguments[2] = mapping.mspointer;
}
break;
default:
arguments[2] = mapping[initType.toLowerCase()];
break;
}
}
event["init" + initType + "Event"].apply(event, Array.prototype.slice.call(arguments, 2));
},
_initMouseEvent: function (event) {
this._initEventImpl.apply(this, ["Mouse", event].concat(Array.prototype.slice.call(arguments, 1)));
},
_initPointerEvent: function (event) {
this._initEventImpl.apply(this, ["Pointer", event].concat(Array.prototype.slice.call(arguments, 1)));
},
_PointerEventProxy: PointerEventProxy,
_bubbleEvent: bubbleEvent,
_setPointerCapture: function (element, pointerId) {
if (element.setPointerCapture) {
element.setPointerCapture(pointerId);
}
},
_releasePointerCapture: function (element, pointerId) {
if (element.releasePointerCapture) {
element.releasePointerCapture(pointerId);
}
},
_MSPointerEvent: _MSPointerEvent,
_getComputedStyle: _getComputedStyle,
_zoomToDuration: _zoomToDuration,
_zoomTo: function _zoomTo(element, args) {
if (this._supportsSnapPoints && element.msZoomTo) {
element.msZoomTo(args);
} else {
// Schedule to ensure that we're not running from within an event handler. For example, if running
// within a focus handler triggered by WinJS.Utilities._setActive, scroll position will not yet be
// restored.
Scheduler.schedule(function () {
var initialPos = getAdjustedScrollPosition(element);
var effectiveScrollLeft = (typeof element._zoomToDestX === "number" ? element._zoomToDestX : initialPos.scrollLeft);
var effectiveScrollTop = (typeof element._zoomToDestY === "number" ? element._zoomToDestY : initialPos.scrollTop);
var cs = _getComputedStyle(element);
var scrollLimitX = element.scrollWidth - parseInt(cs.width, 10) - parseInt(cs.paddingLeft, 10) - parseInt(cs.paddingRight, 10);
var scrollLimitY = element.scrollHeight - parseInt(cs.height, 10) - parseInt(cs.paddingTop, 10) - parseInt(cs.paddingBottom, 10);
if (typeof args.contentX !== "number") {
args.contentX = effectiveScrollLeft;
}
if (typeof args.contentY !== "number") {
args.contentY = effectiveScrollTop;
}
var zoomToDestX = _clamp(args.contentX, 0, scrollLimitX);
var zoomToDestY = _clamp(args.contentY, 0, scrollLimitY);
if (zoomToDestX === effectiveScrollLeft && zoomToDestY === effectiveScrollTop) {
// Scroll position is already in the proper state. This zoomTo is a no-op.
return;
}
element._zoomToId = element._zoomToId || 0;
element._zoomToId++;
element._zoomToDestX = zoomToDestX;
element._zoomToDestY = zoomToDestY;
var thisZoomToId = element._zoomToId;
var start = _BaseUtils._now();
var xFactor = (element._zoomToDestX - initialPos.scrollLeft) / _zoomToDuration;
var yFactor = (element._zoomToDestY - initialPos.scrollTop) / _zoomToDuration;
var update = function () {
var t = _BaseUtils._now() - start;
if (element._zoomToId !== thisZoomToId) {
return;
} else if (t > _zoomToDuration) {
setAdjustedScrollPosition(element, element._zoomToDestX, element._zoomToDestY);
element._zoomToDestX = null;
element._zoomToDestY = null;
} else {
setAdjustedScrollPosition(element, initialPos.scrollLeft + t * xFactor, initialPos.scrollTop + t * yFactor);
_BaseUtils._requestAnimationFrame(update);
}
};
_BaseUtils._requestAnimationFrame(update);
}, Scheduler.Priority.high, null, "WinJS.Utilities._zoomTo");
}
},
_setActive: function _setActive(element, scroller) {
var success = true;
try {
if (_Global.HTMLElement && _Global.HTMLElement.prototype.setActive) {
element.setActive();
} else {
// We are aware that, unlike setActive(), focus() will scroll to the element that gets focus. However, this is
// our current cross-browser solution until there is an equivalent for setActive() in other browsers.
//
// This _setActive polyfill does have limited support for preventing scrolling: via the scroller parameter, it
// can prevent one scroller from scrolling. This functionality is necessary in some scenarios. For example, when using
// _zoomTo and _setActive together.
var scrollLeft,
scrollTop;
if (scroller) {
scrollLeft = scroller.scrollLeft;
scrollTop = scroller.scrollTop;
}
element.focus();
if (scroller) {
scroller.scrollLeft = scrollLeft;
scroller.scrollTop = scrollTop;
}
}
} catch (e) {
// setActive() raises an exception when trying to focus an invisible item. Checking visibility is non-trivial, so it's best
// just to catch the exception and ignore it. focus() on the other hand, does not raise exceptions.
success = false;
}
return success;
},
_MutationObserver: _MutationObserver,
_resizeNotifier: {
get: function () {
if (!_resizeNotifier) {
_resizeNotifier = new ResizeNotifier();
}
return _resizeNotifier;
}
},
_GenericListener: GenericListener,
_globalListener: new GenericListener("Global", _Global, { registerThruWinJSCustomEvents: true }),
_documentElementListener: new GenericListener("DocumentElement", _Global.document.documentElement, { registerThruWinJSCustomEvents: true }),
_inputPaneListener: _WinRT.Windows.UI.ViewManagement.InputPane ?
new GenericListener("InputPane", _WinRT.Windows.UI.ViewManagement.InputPane.getForCurrentView()) :
{ addEventListener: function () { }, removeEventListener: function () { } },
// Appends a hidden child to the given element that will listen for being added
// to the DOM. When the hidden element is added to the DOM, it will dispatch a
// "WinJSNodeInserted" event on the provided element.
_addInsertedNotifier: function (element) {
var hiddenElement = _Global.document.createElement("div");
hiddenElement.style[_BaseUtils._browserStyleEquivalents["animation-name"].scriptName] = "WinJS-node-inserted";
hiddenElement.style[_BaseUtils._browserStyleEquivalents["animation-duration"].scriptName] = "0.01s";
hiddenElement.style["position"] = "absolute";
element.appendChild(hiddenElement);
exports._addEventListener(hiddenElement, "animationStart", function (e) {
if (e.animationName === "WinJS-node-inserted") {
var e = _Global.document.createEvent("Event");
e.initEvent("WinJSNodeInserted", false, true);
element.dispatchEvent(e);
}
}, false);
return hiddenElement;
},
// Returns a promise which completes when *element* is in the DOM.
_inDom: function Utilities_inDom(element) {
return new Promise(function (c) {
if (_Global.document.body.contains(element)) {
c();
} else {
var nodeInsertedHandler = function () {
element.removeEventListener("WinJSNodeInserted", nodeInsertedHandler, false);
c();
};
exports._addInsertedNotifier(element);
element.addEventListener("WinJSNodeInserted", nodeInsertedHandler, false);
}
});
},
// Browser agnostic method to set element flex style
// Param is an object in the form {grow: flex-grow, shrink: flex-shrink, basis: flex-basis}
// All fields optional
_setFlexStyle: function (element, flexParams) {
var styleObject = element.style;
if (typeof flexParams.grow !== "undefined") {
styleObject.msFlexPositive = flexParams.grow;
styleObject.webkitFlexGrow = flexParams.grow;
styleObject.flexGrow = flexParams.grow;
}
if (typeof flexParams.shrink !== "undefined") {
styleObject.msFlexNegative = flexParams.shrink;
styleObject.webkitFlexShrink = flexParams.shrink;
styleObject.flexShrink = flexParams.shrink;
}
if (typeof flexParams.basis !== "undefined") {
styleObject.msFlexPreferredSize = flexParams.basis;
styleObject.webkitFlexBasis = flexParams.basis;
styleObject.flexBasis = flexParams.basis;
}
},
///
/// Defines a set of keyboard values.
///
Key: {
///
/// BACKSPACE key.
///
backspace: 8,
///
/// TAB key.
///
tab: 9,
///
/// ENTER key.
///
enter: 13,
///
/// Shift key.
///
shift: 16,
///
/// CTRL key.
///
ctrl: 17,
///
/// ALT key
///
alt: 18,
///
/// Pause key.
///
pause: 19,
///
/// CAPS LOCK key.
///
capsLock: 20,
///
/// ESCAPE key.
///
escape: 27,
///
/// SPACE key.
///
space: 32,
///
/// PAGE UP key.
///
pageUp: 33,
///
/// PAGE DOWN key.
///
pageDown: 34,
///
/// END key.
///
end: 35,
///
/// HOME key.
///
home: 36,
///
/// Left arrow key.
///
leftArrow: 37,
///
/// Up arrow key.
///
upArrow: 38,
///
/// Right arrow key.
///
rightArrow: 39,
///
/// Down arrow key.
///
downArrow: 40,
///
/// INSERT key.
///
insert: 45,
///
/// DELETE key.
///
deleteKey: 46,
///
/// Number 0 key.
///
num0: 48,
///
/// Number 1 key.
///
num1: 49,
///
/// Number 2 key.
///
num2: 50,
///
/// Number 3 key.
///
num3: 51,
///
/// Number 4 key.
///
num4: 52,
///
/// Number 5 key.
///
num5: 53,
///
/// Number 6 key.
///
num6: 54,
///
/// Number 7 key.
///
num7: 55,
///
/// Number 8 key.
///
num8: 56,
///
/// Number 9 key.
///
num9: 57,
///
/// A key.
///
a: 65,
///
/// B key.
///
b: 66,
///
/// C key.
///
c: 67,
///
/// D key.
///
d: 68,
///
/// E key.
///
e: 69,
///
/// F key.
///
f: 70,
///
/// G key.
///
g: 71,
///
/// H key.
///
h: 72,
///
/// I key.
///
i: 73,
///
/// J key.
///
j: 74,
///
/// K key.
///
k: 75,
///
/// L key.
///
l: 76,
///
/// M key.
///
m: 77,
///
/// N key.
///
n: 78,
///
/// O key.
///
o: 79,
///
/// P key.
///
p: 80,
///
/// Q key.
///
q: 81,
///
/// R key.
///
r: 82,
///
/// S key.
///
s: 83,
///
/// T key.
///
t: 84,
///
/// U key.
///
u: 85,
///
/// V key.
///
v: 86,
///
/// W key.
///
w: 87,
///
/// X key.
///
x: 88,
///
/// Y key.
///
y: 89,
///
/// Z key.
///
z: 90,
///
/// Left Windows key.
///
leftWindows: 91,
///
/// Right Windows key.
///
rightWindows: 92,
///
/// Menu key.
///
menu: 93,
///
/// Number pad 0 key.
///
numPad0: 96,
///
/// Number pad 1 key.
///
numPad1: 97,
///
/// Number pad 2 key.
///
numPad2: 98,
///
/// Number pad 3 key.
///
numPad3: 99,
///
/// Number pad 4 key.
///
numPad4: 100,
///
/// Number pad 5 key.
///
numPad5: 101,
///
/// Number pad 6 key.
///
numPad6: 102,
///
/// Number pad 7 key.
///
numPad7: 103,
///
/// Number pad 8 key.
///
numPad8: 104,
///
/// Number pad 9 key.
///
numPad9: 105,
///
/// Multiplication key.
///
multiply: 106,
///
/// Addition key.
///
add: 107,
///
/// Subtraction key.
///
subtract: 109,
///
/// Decimal point key.
///
decimalPoint: 110,
///
/// Division key.
///
divide: 111,
///
/// F1 key.
///
F1: 112,
///
/// F2 key.
///
F2: 113,
///
/// F3 key.
///
F3: 114,
///
/// F4 key.
///
F4: 115,
///
/// F5 key.
///
F5: 116,
///
/// F6 key.
///
F6: 117,
///
/// F7 key.
///
F7: 118,
///
/// F8 key.
///
F8: 119,
///
/// F9 key.
///
F9: 120,
///
/// F10 key.
///
F10: 121,
///
/// F11 key.
///
F11: 122,
///
/// F12 key.
///
F12: 123,
///
/// XBox One Remote NavigationView key.
///
NavigationView: 136,
///
/// XBox One Remote NavigationMenu key.
///
NavigationMenu: 137,
///
/// XBox One Remote NavigationUp key.
///
NavigationUp: 138,
///
/// XBox One Remote NavigationDown key.
///
NavigationDown: 139,
///
/// XBox One Remote NavigationLeft key.
///
NavigationLeft: 140,
///
/// XBox One Remote NavigationRight key.
///
NavigationRight: 141,
///
/// XBox One Remote NavigationAccept key.
///
NavigationAccept: 142,
///
/// XBox One Remote NavigationCancel key.
///
NavigationCancel: 143,
///
/// NUMBER LOCK key.
///
numLock: 144,
///
/// SCROLL LOCK key.
///
scrollLock: 145,
///
/// Browser back key.
///
browserBack: 166,
///
/// Browser forward key.
///
browserForward: 167,
///
/// SEMICOLON key.
///
semicolon: 186,
///
/// EQUAL key.
///
equal: 187,
///
/// COMMA key.
///
comma: 188,
///
/// DASH key.
///
dash: 189,
///
/// PERIOD key.
///
period: 190,
///
/// FORWARD SLASH key.
///
forwardSlash: 191,
///
/// Accent grave key.
///
graveAccent: 192,
///
/// XBox One GamepadA key.
///
GamepadA: 195,
///
/// XBox One GamepadB key.
///
GamepadB: 196,
///
/// XBox One GamepadX key.
///
GamepadX: 197,
///
/// XBox One GamepadY key.
///
GamepadY: 198,
///
/// XBox One GamepadRightShoulder key.
///
GamepadRightShoulder: 199,
///
/// XBox One GamepadLeftShoulder key.
///
GamepadLeftShoulder: 200,
///
/// XBox One GamepadLeftTrigger key.
///
GamepadLeftTrigger: 201,
///
/// XBox One GamepadRightTrigger key.
///
GamepadRightTrigger: 202,
///
/// XBox One GamepadDPadUp key.
///
GamepadDPadUp: 203,
///
/// XBox One GamepadDPadDown key.
///
GamepadDPadDown: 204,
///
/// XBox One GamepadDPadLeft key.
///
GamepadDPadLeft: 205,
///
/// XBox One GamepadDPadRight key.
///
GamepadDPadRight: 206,
///
/// XBox One GamepadMenu key.
///
GamepadMenu: 207,
///
/// XBox One GamepadView key.
///
GamepadView: 208,
///
/// XBox One GamepadLeftThumbstick key.
///
GamepadLeftThumbstick: 209,
///
/// XBox One GamepadRightThumbstick key.
///
GamepadRightThumbstick: 210,
///
/// XBox One GamepadLeftThumbstickUp key.
///
GamepadLeftThumbstickUp: 211,
///
/// XBox One GamepadLeftThumbstickDown key.
///
GamepadLeftThumbstickDown: 212,
///
/// XBox One GamepadLeftThumbstickRight key.
///
GamepadLeftThumbstickRight: 213,
///
/// XBox One GamepadLeftThumbstickLeft key.
///
GamepadLeftThumbstickLeft: 214,
///
/// XBox One GamepadRightThumbstickUp key.
///
GamepadRightThumbstickUp: 215,
///
/// XBox One GamepadRightThumbstickDown key.
///
GamepadRightThumbstickDown: 216,
///
/// XBox One GamepadRightThumbstickRight key.
///
GamepadRightThumbstickRight: 217,
///
/// XBox One GamepadRightThumbstickLeft key.
///
GamepadRightThumbstickLeft: 218,
///
/// OPEN BRACKET key.
///
openBracket: 219,
///
/// BACKSLASH key.
///
backSlash: 220,
///
/// CLOSE BRACKET key.
///
closeBracket: 221,
///
/// SINGLE QUOTE key.
///
singleQuote: 222,
///
/// Any IME input.
///
IME: 229
},
data: function (element) {
///
///
/// Gets the data value associated with the specified element.
///
///
/// The element.
///
///
/// The value associated with the element.
///
///
if (!element[_dataKey]) {
element[_dataKey] = {};
}
return element[_dataKey];
},
hasClass: function (e, name) {
///
///
/// Determines whether the specified element has the specified class.
///
///
/// The element.
///
///
/// The name of the class.
///
///
/// true if the specified element contains the specified class; otherwise, false.
///
///
if (e.classList) {
return e.classList.contains(name);
} else {
var className = getClassName(e);
var names = className.trim().split(" ");
var l = names.length;
for (var i = 0; i < l; i++) {
if (names[i] === name) {
return true;
}
}
return false;
}
},
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
_setAttribute: setAttribute,
getRelativeLeft: function (element, parent) {
///
///
/// Gets the left coordinate of the specified element relative to the specified parent.
///
///
/// The element.
///
///
/// The parent element.
///
///
/// The relative left coordinate.
///
///
if (!element) {
return 0;
}
var elementPosition = exports._getPositionRelativeTo(element, null);
var parentPosition = exports._getPositionRelativeTo(parent, null);
return elementPosition.left - parentPosition.left;
},
getRelativeTop: function (element, parent) {
///
///
/// Gets the top coordinate of the element relative to the specified parent.
///
///
/// The element.
///
///
/// The parent element.
///
///
/// The relative top coordinate.
///
///
if (!element) {
return 0;
}
var elementPosition = exports._getPositionRelativeTo(element, null);
var parentPosition = exports._getPositionRelativeTo(parent, null);
return elementPosition.top - parentPosition.top;
},
getScrollPosition: getScrollPosition,
setScrollPosition: setScrollPosition,
empty: function (element) {
///
///
/// Removes all the child nodes from the specified element.
///
///
/// The element.
///
///
/// The element.
///
///
if (element.childNodes && element.childNodes.length > 0) {
for (var i = element.childNodes.length - 1; i >= 0; i--) {
element.removeChild(element.childNodes.item(i));
}
}
return element;
},
_isDOMElement: function (element) {
return element &&
typeof element === "object" &&
typeof element.tagName === "string";
},
getContentWidth: function (element) {
///
///
/// Gets the width of the content of the specified element. The content width does not include borders or padding.
///
///
/// The element.
///
///
/// The content width of the element.
///
///
var border = getDimension(element, "borderLeftWidth") + getDimension(element, "borderRightWidth"),
padding = getDimension(element, "paddingLeft") + getDimension(element, "paddingRight");
return element.offsetWidth - border - padding;
},
_getPreciseContentWidth: function (element) {
var border = _getPreciseDimension(element, "borderLeftWidth") + _getPreciseDimension(element, "borderRightWidth"),
padding = _getPreciseDimension(element, "paddingLeft") + _getPreciseDimension(element, "paddingRight");
return element.offsetWidth - border - padding;
},
getTotalWidth: function (element) {
///
///
/// Gets the width of the element, including margins.
///
///
/// The element.
///
///
/// The width of the element including margins.
///
///
var margin = getDimension(element, "marginLeft") + getDimension(element, "marginRight");
return element.offsetWidth + margin;
},
_getPreciseTotalWidth: function (element) {
var margin = _getPreciseDimension(element, "marginLeft") + _getPreciseDimension(element, "marginRight");
return element.offsetWidth + margin;
},
getContentHeight: function (element) {
///
///
/// Gets the height of the content of the specified element. The content height does not include borders or padding.
///
///
/// The element.
///
///
/// The content height of the element.
///
///
var border = getDimension(element, "borderTopWidth") + getDimension(element, "borderBottomWidth"),
padding = getDimension(element, "paddingTop") + getDimension(element, "paddingBottom");
return element.offsetHeight - border - padding;
},
_getPreciseContentHeight: function (element) {
var border = _getPreciseDimension(element, "borderTopWidth") + _getPreciseDimension(element, "borderBottomWidth"),
padding = _getPreciseDimension(element, "paddingTop") + _getPreciseDimension(element, "paddingBottom");
return element.offsetHeight - border - padding;
},
getTotalHeight: function (element) {
///
///
/// Gets the height of the element, including its margins.
///
///
/// The element.
///
///
/// The height of the element including margins.
///
///
var margin = getDimension(element, "marginTop") + getDimension(element, "marginBottom");
return element.offsetHeight + margin;
},
_getPreciseTotalHeight: function (element) {
var margin = _getPreciseDimension(element, "marginTop") + _getPreciseDimension(element, "marginBottom");
return element.offsetHeight + margin;
},
getPosition: function (element) {
///
///
/// Gets the position of the specified element.
///
///
/// The element.
///
///
/// An object that contains the left, top, width and height properties of the element.
///
///
return exports._getPositionRelativeTo(element, null);
},
getTabIndex: function (element) {
///
///
/// Gets the tabIndex of the specified element.
///
///
/// The element.
///
///
/// The tabIndex of the element. Returns -1 if the element cannot be tabbed to
///
///
// For reference: http://www.w3.org/html/wg/drafts/html/master/single-page.html#specially-focusable
var tabbableElementsRE = /BUTTON|COMMAND|MENUITEM|OBJECT|SELECT|TEXTAREA/;
if (element.disabled) {
return -1;
}
var tabIndex = element.getAttribute("tabindex");
if (tabIndex === null || tabIndex === undefined) {
var name = element.tagName;
if (tabbableElementsRE.test(name) ||
(element.href && (name === "A" || name === "AREA" || name === "LINK")) ||
(name === "INPUT" && element.type !== "hidden") ||
(name === "TH" && element.sorted)) {
return 0;
}
return -1;
}
return parseInt(tabIndex, 10);
},
convertToPixels: convertToPixels,
_convertToPrecisePixels: _convertToPrecisePixels,
_getPreciseMargins: _getPreciseMargins,
eventWithinElement: function (element, event) {
///
///
/// Determines whether the specified event occurred within the specified element.
///
///
/// The element.
///
///
/// The event.
///
///
/// true if the event occurred within the element; otherwise, false.
///
///
var related = event.relatedTarget;
if (related && related !== element) {
return element.contains(related);
}
return false;
},
//UI Utilities
_deprecated: function (message) {
_Global.console && _Global.console.warn(message);
},
// Take a renderer which may be a function (signature: (data) => element) or a WinJS.Binding.Template
// and return a function with a unified synchronous contract which is:
//
// (data, container) => element
//
// Where:
//
// 1) if you pass container the content will be rendered into the container and the
// container will be returned.
//
// 2) if you don't pass a container the content will be rendered and returned.
//
_syncRenderer: function (renderer, tagName) {
tagName = tagName || "div";
if (typeof renderer === "function") {
return function (data, container) {
if (container) {
container.appendChild(renderer(data));
return container;
} else {
return renderer(data);
}
};
}
var template;
if (typeof renderer.render === "function") {
template = renderer;
} else if (renderer.winControl && typeof renderer.winControl.render === "function") {
template = renderer.winControl;
}
return function (data, container) {
var host = container || _Global.document.createElement(tagName);
template.render(data, host);
if (container) {
return container;
} else {
// The expectation is that the creation of the DOM elements happens synchronously
// and as such we steal the first child and make it the root element.
//
var element = host.firstElementChild;
// Because we have changed the "root" we may need to move the dispose method
// created by the template to the child and do a little switcheroo on dispose.
//
if (element && host.dispose) {
var prev = element.dispose;
element.dispose = function () {
element.dispose = prev;
host.appendChild(element);
host.dispose();
};
}
return element;
}
};
},
_getPositionRelativeTo: function Utilities_getPositionRelativeTo(element, ancestor) {
var fromElement = element,
offsetParent = element.offsetParent,
top = element.offsetTop,
left = element.offsetLeft;
while ((element = element.parentNode) &&
element !== ancestor &&
element !== _Global.document.body &&
element !== _Global.document.documentElement) {
top -= element.scrollTop;
var dir = _getComputedStyle(element, null).direction;
left -= dir !== "rtl" ? element.scrollLeft : -getAdjustedScrollPosition(element).scrollLeft;
if (element === offsetParent) {
top += element.offsetTop;
left += element.offsetLeft;
offsetParent = element.offsetParent;
}
}
return {
left: left,
top: top,
width: fromElement.offsetWidth,
height: fromElement.offsetHeight
};
},
// *element* is not included in the tabIndex search
_getHighAndLowTabIndices: function Utilities_getHighAndLowTabIndices(element) {
var descendants = element.getElementsByTagName("*");
var lowestTabIndex = 0;
var highestTabIndex = 0;
// tabIndex=0 is the highest (considered higher than positive tab indices) so
// we can stop searching for a higher tab index once we find tabIndex=0.
var foundTabIndex0 = false;
for (var i = 0, len = descendants.length; i < len; i++) {
var tabIndexStr = descendants[i].getAttribute("tabIndex");
if (tabIndexStr !== null && tabIndexStr !== undefined) {
var tabIndex = parseInt(tabIndexStr, 10);
// Update lowest
if (tabIndex > 0 && (tabIndex < lowestTabIndex || lowestTabIndex === 0)) {
lowestTabIndex = tabIndex;
}
// Update highest
if (!foundTabIndex0) {
if (tabIndex === 0) {
foundTabIndex0 = true;
highestTabIndex = 0;
} else if (tabIndex > highestTabIndex) {
highestTabIndex = tabIndex;
}
}
}
}
return {
highest: highestTabIndex,
lowest: lowestTabIndex
};
},
_getLowestTabIndexInList: function Utilities_getLowestTabIndexInList(elements) {
// Returns the lowest positive tabIndex in a list of elements.
// Returns 0 if there are no positive tabIndices.
var lowestTabIndex = 0;
var elmTabIndex;
for (var i = 0; i < elements.length; i++) {
elmTabIndex = parseInt(elements[i].getAttribute("tabIndex"), 10);
if ((0 < elmTabIndex)
&& ((elmTabIndex < lowestTabIndex) || !lowestTabIndex)) {
lowestTabIndex = elmTabIndex;
}
}
return lowestTabIndex;
},
_getHighestTabIndexInList: function Utilities_getHighestTabIndexInList(elements) {
// Returns 0 if any element is explicitly set to 0. (0 is the highest tabIndex)
// Returns the highest tabIndex in the list of elements.
// Returns 0 if there are no positive tabIndices.
var highestTabIndex = 0;
var elmTabIndex;
for (var i = 0; i < elements.length; i++) {
elmTabIndex = parseInt(elements[i].getAttribute("tabIndex"), 10);
if (elmTabIndex === 0) {
return elmTabIndex;
} else if (highestTabIndex < elmTabIndex) {
highestTabIndex = elmTabIndex;
}
}
return highestTabIndex;
},
_hasCursorKeysBehaviors: function Utilities_hasCursorKeysBehaviors(element) {
if (element.tagName === "SELECT" ||
element.tagName === "TEXTAREA") {
return true;
}
if (element.tagName === "INPUT") {
return element.type === "" ||
element.type === "date" ||
element.type === "datetime" ||
element.type === "datetime-local" ||
element.type === "email" ||
element.type === "month" ||
element.type === "number" ||
element.type === "password" ||
element.type === "range" ||
element.type === "search" ||
element.type === "tel" ||
element.type === "text" ||
element.type === "time" ||
element.type === "url" ||
element.type === "week";
}
return false;
},
_reparentChildren: function (originalParent, destinationParent) {
var child = originalParent.firstChild;
while (child) {
var sibling = child.nextSibling;
destinationParent.appendChild(child);
child = sibling;
}
},
// Ensures that the same element has focus before and after *callback* is
// called. Useful if moving focus is an unintentional side effect of *callback*.
// For example, this could happen if *callback* removes and reinserts elements
// to the DOM.
_maintainFocus: function ElementUtilities_maintainFocus(callback) {
var focusedElement = _Global.document.activeElement;
callback();
exports._trySetActiveOnAnyElement(focusedElement);
},
// Tries to give focus to an element (even if its tabIndex is -1) via setActive.
_trySetActiveOnAnyElement: function Utilities_trySetActiveOnAnyElement(element, scroller) {
return exports._tryFocusOnAnyElement(element, true, scroller);
},
// Tries to give focus to an element (even if its tabIndex is -1).
_tryFocusOnAnyElement: function Utilities_tryFocusOnAnyElement(element, useSetActive, scroller) {
var previousActiveElement = _Global.document.activeElement;
if (element === previousActiveElement) {
return true;
}
if (useSetActive) {
exports._setActive(element, scroller);
} else {
element.focus();
}
return previousActiveElement !== _Global.document.activeElement;
},
// Tries to give focus to an element which is a tabstop (i.e. tabIndex >= 0)
// via setActive.
_trySetActive: function Utilities_trySetActive(elem, scroller) {
return this._tryFocus(elem, true, scroller);
},
// Tries to give focus to an element which is a tabstop (i.e. tabIndex >= 0).
_tryFocus: function Utilities_tryFocus(elem, useSetActive, scroller) {
var previousActiveElement = _Global.document.activeElement;
if (elem === previousActiveElement) {
return true;
}
var simpleLogicForValidTabStop = (exports.getTabIndex(elem) >= 0);
if (!simpleLogicForValidTabStop) {
return false;
}
if (useSetActive) {
exports._setActive(elem, scroller);
} else {
elem.focus();
}
if (previousActiveElement !== _Global.document.activeElement) {
return true;
}
return false;
},
_setActiveFirstFocusableElement: function Utilities_setActiveFirstFocusableElement(rootEl, scroller) {
return this._focusFirstFocusableElement(rootEl, true, scroller);
},
_focusFirstFocusableElement: function Utilities_focusFirstFocusableElement(rootEl, useSetActive, scroller) {
var _elms = rootEl.getElementsByTagName("*");
// Get the tabIndex set to the firstDiv (which is the lowest)
var _lowestTabIndex = this._getLowestTabIndexInList(_elms);
var _nextLowestTabIndex = 0;
// If there are positive tabIndices, set focus to the element with the lowest tabIndex.
// Keep trying with the next lowest tabIndex until all tabIndices have been exhausted.
// Otherwise set focus to the first focusable element in DOM order.
var i;
while (_lowestTabIndex) {
for (i = 0; i < _elms.length; i++) {
if (_elms[i].tabIndex === _lowestTabIndex) {
if (this._tryFocus(_elms[i], useSetActive, scroller)) {
return true;
}
} else if ((_lowestTabIndex < _elms[i].tabIndex)
&& ((_elms[i].tabIndex < _nextLowestTabIndex) || (_nextLowestTabIndex === 0))) {
// Here if _lowestTabIndex < _elms[i].tabIndex < _nextLowestTabIndex
_nextLowestTabIndex = _elms[i].tabIndex;
}
}
// We weren't able to set focus to anything at that tabIndex
// If we found a higher valid tabIndex, try that now
_lowestTabIndex = _nextLowestTabIndex;
_nextLowestTabIndex = 0;
}
// Wasn't able to set focus to anything with a positive tabIndex, try everything now.
// This is where things with tabIndex of 0 will be tried.
for (i = 0; i < _elms.length; i++) {
if (this._tryFocus(_elms[i], useSetActive, scroller)) {
return true;
}
}
return false;
},
_setActiveLastFocusableElement: function Utilities_setActiveLastFocusableElement(rootEl, scroller) {
return this._focusLastFocusableElement(rootEl, true, scroller);
},
_focusLastFocusableElement: function Utilities_focusLastFocusableElement(rootEl, useSetActive, scroller) {
var _elms = rootEl.getElementsByTagName("*");
// Get the tabIndex set to the finalDiv (which is the highest)
var _highestTabIndex = this._getHighestTabIndexInList(_elms);
var _nextHighestTabIndex = 0;
// Try all tabIndex 0 first. After this conditional the _highestTabIndex
// should be equal to the highest positive tabIndex.
var i;
if (_highestTabIndex === 0) {
for (i = _elms.length - 1; i >= 0; i--) {
if (_elms[i].tabIndex === _highestTabIndex) {
if (this._tryFocus(_elms[i], useSetActive, scroller)) {
return true;
}
} else if (_nextHighestTabIndex < _elms[i].tabIndex) {
_nextHighestTabIndex = _elms[i].tabIndex;
}
}
_highestTabIndex = _nextHighestTabIndex;
_nextHighestTabIndex = 0;
}
// If there are positive tabIndices, set focus to the element with the highest tabIndex.
// Keep trying with the next highest tabIndex until all tabIndices have been exhausted.
// Otherwise set focus to the last focusable element in DOM order.
while (_highestTabIndex) {
for (i = _elms.length - 1; i >= 0; i--) {
if (_elms[i].tabIndex === _highestTabIndex) {
if (this._tryFocus(_elms[i], useSetActive, scroller)) {
return true;
}
} else if ((_nextHighestTabIndex < _elms[i].tabIndex) && (_elms[i].tabIndex < _highestTabIndex)) {
// Here if _nextHighestTabIndex < _elms[i].tabIndex < _highestTabIndex
_nextHighestTabIndex = _elms[i].tabIndex;
}
}
// We weren't able to set focus to anything at that tabIndex
// If we found a lower valid tabIndex, try that now
_highestTabIndex = _nextHighestTabIndex;
_nextHighestTabIndex = 0;
}
// Wasn't able to set focus to anything with a tabIndex, try everything now
for (i = _elms.length - 2; i > 0; i--) {
if (this._tryFocus(_elms[i], useSetActive, scroller)) {
return true;
}
}
return false;
}
});
});