' +
// Create a proxy element inside the canvas so that during an MSPointerDown event we can call
// msSetPointerCapture on it. This allows hover to not be passed to it which saves a large invalidation.
'' +
'
' +
'' +
'
' +
// The keyboard event helper is a dummy node that allows us to keep getting keyboard events when a virtualized element
// gets discarded. It has to be positioned in the center of the viewport, though, otherwise calling .focus() on it
// can move our viewport around when we don't want it moved.
// The keyboard event helper element will be skipped in the tab order if it doesn't have width+height set on it.
'
' +
'' +
'
';
this._viewport = this._element.firstElementChild;
this._canvas = this._viewport.firstElementChild;
this._canvasProxy = this._canvas.firstElementChild;
// The deleteWrapper div is used to maintain the scroll width (after delete(s)) until the animation is done
this._deleteWrapper = this._canvas.nextElementSibling;
this._keyboardEventsHelper = this._viewport.nextElementSibling.firstElementChild;
this._tabIndex = this._element.tabIndex !== undefined ? this._element.tabIndex : 0;
this._tabEventsHelper = new WinJS.UI.TabContainer(this._keyboardEventsHelper.parentNode);
this._tabEventsHelper.tabIndex = this._tabIndex;
this._resetItemCanvas();
this._progressBar = document.createElement("progress");
utilities.addClass(this._progressBar, WinJS.UI._progressClass);
this._progressBar.style.position = "absolute";
this._progressBar.max = 100;
},
_unsetFocusOnItem: function ListView_unsetFocusOnItem(newFocusExists) {
if (this._tabManager.childFocus) {
this._clearFocusRectangle(this._tabManager.childFocus);
}
if (this._isZombie()) {
return;
}
if (!newFocusExists) {
// _setFocusOnItem may run asynchronously so prepare the keyboardEventsHelper
// to receive focus.
if (this._tabManager.childFocus) {
this._tabManager.childFocus = null;
}
this._keyboardEventsHelper._shouldHaveFocus = false;
this._tabEventsHelper.childFocus = this._keyboardEventsHelper;
// If the viewport has focus, leave it there. This will prevent focus from jumping
// from the viewport to the keyboardEventsHelper when scrolling with Narrator Touch.
if (document.activeElement !== this._viewport && this._hasKeyboardFocus) {
this._keyboardEventsHelper._shouldHaveFocus = true;
try {
this._keyboardEventsHelper.setActive();
} catch (ex) {
}
}
}
this._itemFocused = false;
},
_setFocusOnItem: function ListView_setFocusOnItem(entity) {
this._writeProfilerMark("_setFocusOnItem,info");
if (this._focusRequest) {
this._focusRequest.cancel();
}
if (this._isZombie()) {
return;
}
var that = this;
var setFocusOnItemImpl = function (item) {
if (that._isZombie()) {
return;
}
that._tabEventsHelper.childFocus = null;
if (that._tabManager.childFocus !== item) {
that._tabManager.childFocus = item;
}
that._focusRequest = null;
if (that._hasKeyboardFocus && !that._itemFocused) {
if (that._selection._keyboardFocused()) {
that._drawFocusRectangle(item);
}
//#DBG _ASSERT(that._cachedCount !== WinJS.UI._UNINITIALIZED);
// The requestItem promise just completed so _cachedCount will
// be initialized.
that._view.updateAriaForAnnouncement(item, (entity.type === WinJS.UI.ObjectType.groupHeader ? that._groups.length() : that._cachedCount));
// Some consumers of ListView listen for item invoked events and hide the listview when an item is clicked.
// Since keyboard interactions rely on async operations, sometimes an invoke event can be received before we get
// to item.setActive(), and the listview will be made invisible. If that happens and we call item.setActive(), an exception
// is raised for trying to focus on an invisible item. Checking visibility is non-trivial, so it's best
// just to catch the exception and ignore it.
try {
that._itemFocused = true;
item.setActive();
} catch (error) { }
}
};
if (entity.type !== WinJS.UI.ObjectType.groupHeader) {
this._focusRequest = this._view.items.requestItem(entity.index).then(setFocusOnItemImpl);
} else {
this._focusRequest = this._groups.requestHeader(entity.index).then(setFocusOnItemImpl);
}
},
_attachEvents: function ListView_attachEvents() {
var that = this;
function listViewHandler(eventName, caseSensitive, capture) {
return {
name: (caseSensitive ? eventName : eventName.toLowerCase()),
handler: function (eventObject) {
that["_on" + eventName](eventObject);
},
capture: capture
};
}
function modeHandler(eventName, caseSensitive, capture) {
return {
capture: capture,
name: (caseSensitive ? eventName : eventName.toLowerCase()),
handler: function (eventObject) {
var currentMode = that._mode,
name = "on" + eventName;
if (!that._disposed && currentMode[name]) {
currentMode[name](eventObject);
}
}
};
}
function observerHandler(handlerName, attributesFilter) {
return {
handler: function (listOfChanges) {
that["_on" + handlerName](listOfChanges);
},
filter: attributesFilter
};
}
// Observers for specific element attribute changes
var elementObservers = [
observerHandler("PropertyChange", ["dir", "style", "tabindex"])
];
this._cachedStyleDir = this._element.style.direction;
elementObservers.forEach(function (elementObserver) {
new MutationObserver(elementObserver.handler).observe(that._element, { attributes: true, attributeFilter: elementObserver.filter });
});
// KeyDown handler needs to be added explicitly via addEventListener instead of using attachEvent.
// If it's not added via addEventListener, the eventObject given to us on event does not have the functions stopPropagation() and preventDefault().
var events = [
modeHandler("PointerDown"),
modeHandler("click", false),
modeHandler("PointerUp"),
modeHandler("LostPointerCapture"),
modeHandler("MSHoldVisual", true),
modeHandler("PointerCancel", true),
modeHandler("DragStart"),
modeHandler("DragOver"),
modeHandler("DragEnter"),
modeHandler("DragLeave"),
modeHandler("Drop"),
modeHandler("ContextMenu"),
modeHandler("MSManipulationStateChanged", true, true)
];
events.forEach(function (eventHandler) {
that._viewport.addEventListener(eventHandler.name, eventHandler.handler, !!eventHandler.capture);
});
// Focus and Blur events need to be handled during the capturing phase, they do not bubble.
var elementEvents = [
listViewHandler("Focus", false, true),
listViewHandler("Blur", false, true),
modeHandler("KeyDown"),
modeHandler("KeyUp"),
listViewHandler("MSElementResize", false, false)
];
elementEvents.forEach(function (eventHandler) {
that._element.addEventListener(eventHandler.name, eventHandler.handler, !!eventHandler.capture);
});
var viewportEvents = [
listViewHandler("MSManipulationStateChanged", true),
listViewHandler("Scroll")
];
viewportEvents.forEach(function (viewportEvent) {
that._viewport.addEventListener(viewportEvent.name, viewportEvent.handler, false);
});
this._keyboardEventsHelper.parentNode.addEventListener("onTabEnter", this._onTabEnter.bind(this), false);
this._keyboardEventsHelper.parentNode.addEventListener("onTabExit", this._onTabExit.bind(this), false);
},
_updateItemsManager: function ListView_updateItemsManager() {
var that = this,
notificationHandler = {
// Following methods are used by ItemsManager
beginNotifications: function ListView_beginNotifications() {
},
changed: function ListView_changed(newItem, oldItem, oldItemObject) {
if (that._ifZombieDispose()) { return; }
that._createUpdater();
//#DBG _ASSERT(utilities._isDOMElement(newItem));
var elementInfo = that._updater.elements[oldItem.uniqueID];
if (elementInfo) {
var selected = that.selection._isIncluded(elementInfo.index);
if (selected) {
that._updater.updateDrag = true;
}
if (oldItem !== newItem) {
if (that._tabManager.childFocus === oldItem || that._updater.newFocusedItem === oldItem) {
that._updater.newFocusedItem = newItem;
that._tabManager.childFocus = null;
}
if (elementInfo.itemBox) {
utilities.addClass(newItem, WinJS.UI._itemClass);
that._setupAriaSelectionObserver(newItem);
var next = oldItem.nextElementSibling;
elementInfo.itemBox.removeChild(oldItem);
elementInfo.itemBox.insertBefore(newItem, next);
}
that._setAriaSelected(newItem, selected);
that._view.items.setItemAt(elementInfo.newIndex, {
element: newItem,
itemBox: elementInfo.itemBox,
container: elementInfo.container,
itemsManagerRecord: elementInfo.itemsManagerRecord
});
delete that._updater.elements[oldItem.uniqueID];
WinJS.Utilities._disposeElement(oldItem);
that._updater.elements[newItem.uniqueID] = {
item: newItem,
container: elementInfo.container,
itemBox: elementInfo.itemBox,
index: elementInfo.index,
newIndex: elementInfo.newIndex,
itemsManagerRecord: elementInfo.itemsManagerRecord
};
} else if (elementInfo.itemBox && elementInfo.container) {
WinJS.UI._ItemEventsHandler.renderSelection(elementInfo.itemBox, newItem, selected, true);
utilities[selected ? "addClass" : "removeClass"](elementInfo.container, WinJS.UI._selectedClass);
}
that._updater.changed = true;
}
for (var i = 0, len = that._notificationHandlers.length; i < len; i++) {
that._notificationHandlers[i].changed(newItem, oldItem);
}
that._writeProfilerMark("changed,info");
},
removed: function ListView_removed(item, mirage, handle) {
if (that._ifZombieDispose()) { return; }
that._createUpdater();
function removeFromSelection(index) {
that._updater.updateDrag = true;
if (that._currentMode()._dragging && that._currentMode()._draggingUnselectedItem && that._currentMode()._dragInfo._isIncluded(index)) {
that._updater.newDragInfo = new WinJS.UI._Selection(that, []);
}
var firstRange = that._updater.selectionFirst[index],
lastRange = that._updater.selectionLast[index],
range = firstRange || lastRange;
if (range) {
delete that._updater.selectionFirst[range.oldFirstIndex];
delete that._updater.selectionLast[range.oldLastIndex];
that._updater.selectionChanged = true;
}
}
var insertedItem = that._insertedItems[handle];
if (insertedItem) {
delete that._insertedItems[handle];
}
var index;
if (item) {
var elementInfo = that._updater.elements[item.uniqueID],
itemObject = that._itemsManager.itemObject(item);
if (itemObject) {
that._groupFocusCache.deleteItem(itemObject.key);
}
if (elementInfo) {
index = elementInfo.index;
// We track removed elements for animation purposes (layout
// component consumes this).
//
if (elementInfo.itemBox) {
that._updater.removed.push({
index: index,
itemBox: elementInfo.itemBox
});
}
that._updater.deletesCount++;
// The view can't access the data from the itemsManager
// anymore, so we need to flag the itemData that it
// has been removed.
//
var itemData = that._view.items.itemDataAt(index);
itemData.removed = true;
/*#DBG
delete elementInfo.itemsManagerRecord.updater;
#DBG*/
delete that._updater.elements[item.uniqueID];
} else {
index = itemObject && itemObject.index;
}
if (that._updater.oldFocus.type !== WinJS.UI.ObjectType.groupHeader && that._updater.oldFocus.index === index) {
that._updater.newFocus.index = index; // If index is too high, it'll be fixed in endNotifications
that._updater.focusedItemRemoved = true;
}
removeFromSelection(index);
} else {
index = that._updater.selectionHandles[handle];
if (index === +index) {
removeFromSelection(index);
}
}
that._writeProfilerMark("removed(" + index + "),info");
that._updater.changed = true;
},
updateAffectedRange: function ListView_updateAffectedRange(newerRange) {
that._itemsCount().then(function (count) {
// When we receive insertion notifications before all of the containers have
// been created and the affected range is beyond the container range, the
// affected range indices will not correspond to the indices of the containers
// created by updateContainers. In this case, start the affected range at the end
// of the containers so that the affected range includes any containers that get
// appended due to this batch of notifications.
var containerCount = that._view.containers ? that._view.containers.length : 0;
newerRange.start = Math.min(newerRange.start, containerCount);
that._affectedRange.add(newerRange, count);
});
that._createUpdater();
that._updater.changed = true;
},
indexChanged: function ListView_indexChanged(item, newIndex, oldIndex) {
// We should receive at most one indexChanged notification per oldIndex
// per notification cycle.
if (that._ifZombieDispose()) { return; }
that._createUpdater();
if (item) {
var itemObject = that._itemsManager.itemObject(item);
if (itemObject) {
that._groupFocusCache.updateItemIndex(itemObject.key, newIndex);
}
var elementInfo = that._updater.elements[item.uniqueID];
if (elementInfo) {
elementInfo.newIndex = newIndex;
that._updater.changed = true;
}
that._updater.itemsMoved = true;
}
if (that._currentMode()._dragging && that._currentMode()._draggingUnselectedItem && that._currentMode()._dragInfo._isIncluded(oldIndex)) {
that._updater.newDragInfo = new WinJS.UI._Selection(that, [{ firstIndex: newIndex, lastIndex: newIndex }]);
that._updater.updateDrag = true;
}
if (that._updater.oldFocus.type !== WinJS.UI.ObjectType.groupHeader && that._updater.oldFocus.index === oldIndex) {
that._updater.newFocus.index = newIndex;
that._updater.changed = true;
}
if (that._updater.oldSelectionPivot === oldIndex) {
that._updater.newSelectionPivot = newIndex;
that._updater.changed = true;
}
var range = that._updater.selectionFirst[oldIndex];
if (range) {
range.newFirstIndex = newIndex;
that._updater.changed = true;
that._updater.updateDrag = true;
}
range = that._updater.selectionLast[oldIndex];
if (range) {
range.newLastIndex = newIndex;
that._updater.changed = true;
that._updater.updateDrag = true;
}
},
endNotifications: function ListView_endNotifications() {
that._update();
},
inserted: function ListView_inserted(itemPromise) {
if (that._ifZombieDispose()) { return; }
that._writeProfilerMark("inserted,info");
that._createUpdater();
that._updater.changed = true;
itemPromise.retain();
that._updater.insertsCount++;
that._insertedItems[itemPromise.handle] = itemPromise;
},
moved: function ListView_moved(item, previous, next, itemPromise) {
if (that._ifZombieDispose()) { return; }
that._createUpdater();
that._updater.movesCount++;
if (item) {
that._updater.itemsMoved = true;
var elementInfo = that._updater.elements[item.uniqueID];
if (elementInfo) {
elementInfo.moved = true;
}
}
var index = that._updater.selectionHandles[itemPromise.handle];
if (index === +index) {
that._updater.updateDrag = true;
var firstRange = that._updater.selectionFirst[index],
lastRange = that._updater.selectionLast[index],
range = firstRange || lastRange;
if (range && range.oldFirstIndex !== range.oldLastIndex) {
delete that._updater.selectionFirst[range.oldFirstIndex];
delete that._updater.selectionLast[range.oldLastIndex];
that._updater.selectionChanged = true;
that._updater.changed = true;
}
}
that._writeProfilerMark("moved(" + index + "),info");
},
countChanged: function ListView_countChanged(newCount, oldCount) {
if (that._ifZombieDispose()) { return; }
that._writeProfilerMark("countChanged(" + newCount + "),info");
//#DBG _ASSERT(newCount !== undefined);
that._cachedCount = newCount;
that._createUpdater();
if ((that._view.lastIndexDisplayed + 1) === oldCount) {
that._updater.changed = true;
}
that._updater.countDifference += newCount - oldCount;
},
reload: function ListView_reload() {
if (that._ifZombieDispose()) {
return;
}
that._writeProfilerMark("reload,info");
that._processReload();
}
};
function statusChanged(eventObject) {
if (eventObject.detail === thisWinUI.DataSourceStatus.failure) {
that.itemDataSource = null;
that.groupDataSource = null;
}
}
if (this._versionManager) {
this._versionManager._dispose();
}
this._versionManager = new WinJS.UI._VersionManager();
this._updater = null;
var ranges = this._selection.getRanges();
this._selection._selected.clear();
if (this._itemsManager) {
if (this._itemsManager.dataSource && this._itemsManager.dataSource.removeEventListener) {
this._itemsManager.dataSource.removeEventListener("statuschanged", statusChanged, false);
}
this._clearInsertedItems();
this._itemsManager.release();
}
if (this._itemsCountPromise) {
this._itemsCountPromise.cancel();
this._itemsCountPromise = null;
}
this._cachedCount = WinJS.UI._UNINITIALIZED;
this._itemsManager = thisWinUI._createItemsManager(
this._dataSource,
this._renderWithoutReuse.bind(this),
notificationHandler,
{
ownerElement: this._element,
versionManager: this._versionManager,
indexInView: function (index) {
return (index >= that.indexOfFirstVisible && index <= that.indexOfLastVisible);
},
viewCallsReady: true,
profilerId: this._id
});
if (this._dataSource.addEventListener) {
this._dataSource.addEventListener("statuschanged", statusChanged, false);
}
this._selection._selected.set(ranges);
},
_processReload: function () {
this._affectedRange.addAll();
// Inform scroll view that a realization pass is coming so that it doesn't restart the
// realization pass itself.
this._cancelAsyncViewWork(true);
if (this._currentMode()._dragging) {
this._currentMode()._clearDragProperties();
}
this._groupFocusCache.clear();
this._selection._reset();
this._updateItemsManager();
this._pendingLayoutReset = true;
this._batchViewUpdates(ViewChange.rebuild, ScrollToPriority.low, this.scrollPosition);
},
_createUpdater: function ListView_createUpdater() {
if (!this._updater) {
if (this.itemDataSource instanceof WinJS.UI.VirtualizedDataSource) {
// VDS doesn't support the _updateAffectedRange notification so assume
// that everything needs to be relaid out.
this._affectedRange.addAll();
}
this._versionManager.beginUpdating();
// Inform scroll view that a realization pass is coming so that it doesn't restart the
// realization pass itself.
this._cancelAsyncViewWork();
var updater = {
changed: false,
elements: {},
selectionFirst: {},
selectionLast: {},
selectionHandles: {},
oldSelectionPivot: { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX },
newSelectionPivot: { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX },
removed: [],
selectionChanged: false,
oldFocus: { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX },
newFocus: { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX },
hadKeyboardFocus: this._hasKeyboardFocus,
itemsMoved: false,
lastVisible: this.indexOfLastVisible,
updateDrag: false,
movesCount: 0,
insertsCount: 0,
deletesCount: 0,
countDifference: 0
};
this._view.items.each(function (index, item, itemData) {
/*#DBG
if (itemData.itemsManagerRecord.released) {
throw "ACK! found released data in items collection";
}
itemData.itemsManagerRecord.updater = updater;
#DBG*/
updater.elements[item.uniqueID] = {
item: item,
container: itemData.container,
itemBox: itemData.itemBox,
index: index,
newIndex: index,
itemsManagerRecord: itemData.itemsManagerRecord,
detached: itemData.detached
};
});
var selection = this._selection._selected._ranges;
for (var i = 0, len = selection.length; i < len; i++) {
var range = selection[i];
var newRange = {
newFirstIndex: selection[i].firstIndex,
oldFirstIndex: selection[i].firstIndex,
newLastIndex: selection[i].lastIndex,
oldLastIndex: selection[i].lastIndex
};
updater.selectionFirst[newRange.oldFirstIndex] = newRange;
updater.selectionLast[newRange.oldLastIndex] = newRange;
updater.selectionHandles[range.firstPromise.handle] = newRange.oldFirstIndex;
updater.selectionHandles[range.lastPromise.handle] = newRange.oldLastIndex;
}
updater.oldSelectionPivot = this._selection._pivot;
updater.newSelectionPivot = updater.oldSelectionPivot;
updater.oldFocus = this._selection._getFocused();
updater.newFocus = this._selection._getFocused();
this._updater = updater;
}
},
_synchronize: function ListView_synchronize() {
var updater = this._updater;
this._updater = null;
var groupsChanged = this._groupsChanged;
this._groupsChanged = false;
/*#DBG
if (updater) {
for (i in updater.elements) {
if (updater.elements.hasOwnProperty(i)) {
var elementInfo = updater.elements[i];
delete elementInfo.itemsManagerRecord.updater;
}
}
}
#DBG*/
this._countDifference = this._countDifference || 0;
if (updater && updater.changed) {
if (updater.itemsMoved) {
this._layout.itemsMoved && this._layout.itemsMoved();
}
if (updater.removed.length) {
this._layout.itemsRemoved && this._layout.itemsRemoved(updater.removed.map(function (node) {
return node.itemBox;
}));
}
if (updater.itemsMoved || updater.removed.length || Object.keys(this._insertedItems).length) {
this._layout.setupAnimations && this._layout.setupAnimations();
}
if (this._currentMode().onDataChanged) {
this._currentMode().onDataChanged();
}
var newSelection = [];
for (var i in updater.selectionFirst) {
if (updater.selectionFirst.hasOwnProperty(i)) {
var range = updater.selectionFirst[i];
updater.selectionChanged = updater.selectionChanged || ((range.newLastIndex - range.newFirstIndex) != (range.oldLastIndex - range.oldFirstIndex));
if (range.newFirstIndex <= range.newLastIndex) {
newSelection.push({
firstIndex: range.newFirstIndex,
lastIndex: range.newLastIndex
});
}
}
}
if (updater.selectionChanged) {
var newSelectionItems = new WinJS.UI._Selection(this, newSelection);
// We do not allow listeners to cancel the selection
// change because the cancellation would also have to
// prevent the deletion.
this._selection._fireSelectionChanging(newSelectionItems);
this._selection._selected.set(newSelection);
this._selection._fireSelectionChanged();
newSelectionItems.clear();
} else {
this._selection._selected.set(newSelection);
}
this._selection._updateCount(this._cachedCount);
updater.newSelectionPivot = Math.min(this._cachedCount - 1, updater.newSelectionPivot);
this._selection._pivot = (updater.newSelectionPivot >= 0 ? updater.newSelectionPivot : WinJS.UI._INVALID_INDEX);
if (updater.newFocus.type !== WinJS.UI.ObjectType.groupHeader) {
updater.newFocus.index = Math.max(0, Math.min(this._cachedCount - 1, updater.newFocus.index));
}
this._selection._setFocused(updater.newFocus, this._selection._keyboardFocused());
// If there are 2 edits before layoutAnimations runs we need to merge the 2 groups of modified elements.
// For example:
// If you start with A, B, C and add item Z to the beginning you will have
// [ -1 -> 0, 0 -> 1, 1 -> 2, 2 -> 3]
// However before layout is called an insert of Y to the beginning also happens you should get
// [ -1 -> 0, -1 -> 1, 0 -> 2, 1 -> 3, 2 -> 4]
var previousModifiedElements = this._modifiedElements || [];
var previousModifiedElementsHash = {};
this._modifiedElements = [];
this._countDifference += updater.countDifference;
for (i = 0; i < previousModifiedElements.length; i++) {
var modifiedElement = previousModifiedElements[i];
if (modifiedElement.newIndex === -1) {
this._modifiedElements.push(modifiedElement);
} else {
previousModifiedElementsHash[modifiedElement.newIndex] = modifiedElement;
}
}
for (i = 0; i < updater.removed.length; i++) {
var removed = updater.removed[i];
var modifiedElement = previousModifiedElementsHash[removed.index];
if (modifiedElement) {
delete previousModifiedElementsHash[removed.index];
} else {
modifiedElement = {
oldIndex: removed.index
};
}
modifiedElement.newIndex = -1;
if (!modifiedElement._removalHandled) {
modifiedElement._itemBox = removed.itemBox;
}
this._modifiedElements.push(modifiedElement);
}
var insertedKeys = Object.keys(this._insertedItems);
for (i = 0; i < insertedKeys.length; i++) {
this._modifiedElements.push({
oldIndex: -1,
newIndex: this._insertedItems[insertedKeys[i]].index
});
}
this._writeProfilerMark("_synchronize:update_modifiedElements,StartTM");
var newItems = {};
for (i in updater.elements) {
if (updater.elements.hasOwnProperty(i)) {
var elementInfo = updater.elements[i];
/*#DBG
if (elementInfo.itemsManagerRecord.released) {
throw "ACK! attempt to put released record into list of items for ScrollView";
}
#DBG*/
newItems[elementInfo.newIndex] = {
element: elementInfo.item,
container: elementInfo.container,
itemBox: elementInfo.itemBox,
itemsManagerRecord: elementInfo.itemsManagerRecord,
detached: elementInfo.detached
};
var modifiedElement = previousModifiedElementsHash[elementInfo.index];
if (modifiedElement) {
delete previousModifiedElementsHash[elementInfo.index];
modifiedElement.newIndex = elementInfo.newIndex;
} else {
modifiedElement = {
oldIndex: elementInfo.index,
newIndex: elementInfo.newIndex
};
}
modifiedElement.moved = elementInfo.moved;
this._modifiedElements.push(modifiedElement);
}
}
this._writeProfilerMark("_synchronize:update_modifiedElements,StopTM");
var previousIndices = Object.keys(previousModifiedElementsHash);
for (i = 0; i < previousIndices.length; i++) {
var key = previousIndices[i];
var modifiedElement = previousModifiedElementsHash[key];
if (modifiedElement.oldIndex !== -1) {
this._modifiedElements.push(modifiedElement);
}
}
this._view.items._itemData = newItems;
if (updater.updateDrag && this._currentMode()._dragging) {
if (!this._currentMode()._draggingUnselectedItem) {
this._currentMode()._dragInfo = this._selection;
} else if (updater.newDragInfo) {
this._currentMode()._dragInfo = updater.newDragInfo;
}
this._currentMode().fireDragUpdateEvent();
}
// If the focused item is removed, or the item we're trying to focus on has been moved before we can focus on it,
// we need to update our focus request to get the item from the appropriate index.
if (updater.focusedItemRemoved || (this._focusRequest && (updater.oldFocus.index !== updater.newFocus.index) || (updater.oldFocus.type !== updater.newFocus.type))) {
this._itemFocused = false;
this._setFocusOnItem(this._selection._getFocused());
} else if (updater.newFocusedItem) {
// We need to restore the value of _hasKeyboardFocus because a changed item
// gets removed from the DOM at the time of the notification. If the item
// had focus at that time, then our value of _hasKeyboardFocus will have changed.
this._hasKeyboardFocus = updater.hadKeyboardFocus;
this._itemFocused = false;
this._setFocusOnItem(this._selection._getFocused());
}
var that = this;
return this._groups.synchronizeGroups().then(function () {
if (updater.newFocus.type === WinJS.UI.ObjectType.groupHeader) {
updater.newFocus.index = Math.min(that._groups.length() - 1, updater.newFocus.index);
if (updater.newFocus.index < 0) {
// An empty listview has currentFocus = item 0
updater.newFocus = { type: WinJS.UI.ObjectType.item, index: 0 };
}
that._selection._setFocused(updater.newFocus, that._selection._keyboardFocused());
}
that._versionManager.endUpdating();
if (updater.deletesCount > 0) {
that._updateDeleteWrapperSize();
}
return that._view.updateTree(that._cachedCount, that._countDifference, that._modifiedElements);
}).then(function () {
return that._lastScrollPosition;
});
} else {
this._countDifference += updater ? updater.countDifference : 0;
var that = this;
return this._groups.synchronizeGroups().then(function ListView_synchronizeGroups_success_groupsChanged() {
updater && that._versionManager.endUpdating();
return that._view.updateTree(that._cachedCount, that._countDifference, that._modifiedElements);
}).then(function () {
return that.scrollPosition;
});
}
},
_updateDeleteWrapperSize: function ListView_updateDeleteWrapperSize(clear) {
var sizeProperty = this._horizontal() ? "width" : "height";
this._deleteWrapper.style["min-" + sizeProperty] = (clear ? 0 : this.scrollPosition + this._getViewportSize()[sizeProperty]) + "px";
},
_verifyRealizationNeededForChange: function ListView_skipRealization() {
// If the updater indicates that only deletes occurred, and we have not lost a viewport full of items,
// we skip realizing all the items and appending new ones until other action causes a full realize (e.g. scrolling).
//
var skipRealization = false;
var totalInViewport = (this._view.lastIndexDisplayed || 0) - (this._view.firstIndexDisplayed || 0);
var deletesOnly = this._updater && this._updater.movesCount === 0 && this._updater.insertsCount === 0 && this._updater.deletesCount > 0 && (this._updater.deletesCount === Math.abs(this._updater.countDifference));
if (deletesOnly && this._updater.elements) {
// Verify that the indices of the elements in the updater are within the valid range
var elementsKeys = Object.keys(this._updater.elements);
for (var i = 0, len = elementsKeys.length; i < len; i++) {
var element = this._updater.elements[elementsKeys[i]];
var delta = element.index - element.newIndex;
if (delta < 0 || delta > this._updater.deletesCount) {
deletesOnly = false;
break;
}
}
}
this._view.deletesWithoutRealize = this._view.deletesWithoutRealize || 0;
if (deletesOnly &&
(this._view.lastIndexDisplayed < this._view.end - totalInViewport) &&
(this._updater.deletesCount + this._view.deletesWithoutRealize) < totalInViewport) {
skipRealization = true;
this._view.deletesWithoutRealize += Math.abs(this._updater.countDifference);
this._writeProfilerMark("skipping realization on delete,info");
} else {
this._view.deletesWithoutRealize = 0;
}
this._view._setSkipRealizationForChange(skipRealization);
},
_update: function ListView_update() {
this._writeProfilerMark("update,StartTM");
if (this._ifZombieDispose()) { return; }
this._updateJob = null;
var that = this;
if (this._versionManager.noOutstandingNotifications) {
if (this._updater || this._groupsChanged) {
this._cancelAsyncViewWork();
this._verifyRealizationNeededForChange();
this._synchronize().then(function (scrollbarPos) {
that._writeProfilerMark("update,StopTM");
that._batchViewUpdates(ViewChange.relayout, ScrollToPriority.low, scrollbarPos).complete();
});
} else {
// Even if nothing important changed we need to restart aria work if it was canceled.
this._batchViewUpdates(ViewChange.relayout, ScrollToPriority.low, this._lastScrollPosition).complete();
}
}
},
_scheduleUpdate: function ListView_scheduleUpdate() {
if (!this._updateJob) {
var that = this;
// Batch calls to _scheduleUpdate
this._updateJob = Scheduler.schedulePromiseHigh(null, "WinJS.UI.ListView._update").then(function () {
if (that._updateJob) {
that._update();
}
});
this._raiseViewLoading();
}
},
_createGroupsContainer: function () {
if (this._groups) {
this._groups.cleanUp();
}
if (this._groupDataSource) {
this._groups = new WinJS.UI._UnvirtualizedGroupsContainer(this, this._groupDataSource);
} else {
this._groups = new WinJS.UI._NoGroups(this);
}
},
_createLayoutSite: function () {
var that = this;
return Object.create({
invalidateLayout: function () {
that._pendingLayoutReset = true;
var orientationChanged = (that._layout.orientation === "horizontal") !== that._horizontalLayout;
that._affectedRange.addAll();
that._batchViewUpdates(ViewChange.rebuild, ScrollToPriority.low, orientationChanged ? 0 : that.scrollPosition, false, true);
},
itemFromIndex: function (itemIndex) {
return that._itemsManager._itemPromiseAtIndex(itemIndex);
},
groupFromIndex: function (groupIndex) {
if (that._groupsEnabled()) {
return groupIndex < that._groups.length() ? that._groups.group(groupIndex).userData : null;
} else {
return { key: "-1" };
}
},
groupIndexFromItemIndex: function (itemIndex) {
// If itemIndex < 0, returns 0. If itemIndex is larger than the
// biggest item index, returns the last group index.
itemIndex = Math.max(0, itemIndex);
return that._groups.groupFromItem(itemIndex);
},
renderItem: function (itemPromise) {
return WinJS.Promise._cancelBlocker(that._itemsManager._itemFromItemPromise(itemPromise)).then(function (element) {
if (element) {
var record = that._itemsManager._recordFromElement(element);
if (record.pendingReady) {
record.pendingReady();
}
element = element.cloneNode(true);
utilities.addClass(element, WinJS.UI._itemClass);
var itemBox = document.createElement("div");
utilities.addClass(itemBox, thisWinUI._itemBoxClass);
itemBox.appendChild(element);
var container = document.createElement("div");
utilities.addClass(container, thisWinUI._containerClass);
container.appendChild(itemBox);
return container;
} else {
return WinJS.Promise.cancel;
}
});
},
renderHeader: function (group) {
var rendered = WinJS.UI._normalizeRendererReturn(that.groupHeaderTemplate(Promise.wrap(group)));
return rendered.then(function (headerRecord) {
utilities.addClass(headerRecord.element, thisWinUI._headerClass);
var container = document.createElement("div");
utilities.addClass(container, thisWinUI._headerContainerClass);
container.appendChild(headerRecord.element);
return container;
});
},
readyToMeasure: function () {
that._getViewportLength();
that._getCanvasMargins();
},
_isZombie: function () {
return that._isZombie();
},
_writeProfilerMark: function (text) {
that._writeProfilerMark(text);
}
}, {
_itemsManager: {
enumerable: true,
get: function () {
return that._itemsManager;
}
},
rtl: {
enumerable: true,
get: function () {
return that._rtl();
}
},
surface: {
enumerable: true,
get: function () {
return that._canvas;
}
},
viewport: {
enumerable: true,
get: function () {
return that._viewport;
}
},
scrollbarPos: {
enumerable: true,
get: function () {
return that.scrollPosition;
}
},
viewportSize: {
enumerable: true,
get: function () {
return that._getViewportSize();
}
},
loadingBehavior: {
enumerable: true,
get: function () {
return that.loadingBehavior;
}
},
animationsDisabled: {
enumerable: true,
get: function () {
return that._animationsDisabled();
}
},
tree: {
enumerable: true,
get: function () {
return that._view.tree;
}
},
realizedRange: {
enumerable: true,
get: function () {
return {
firstPixel: Math.max(0, that.scrollPosition - 2 * that._getViewportLength()),
lastPixel: that.scrollPosition + 3 * that._getViewportLength() - 1
}
}
},
visibleRange: {
enumerable: true,
get: function () {
return {
firstPixel: that.scrollPosition,
lastPixel: that.scrollPosition + that._getViewportLength() - 1
}
}
},
itemCount: {
enumerable: true,
get: function () {
return that._itemsCount();
}
},
groupCount: {
enumerable: true,
get: function () {
return that._groups.length();
}
}
});
},
_initializeLayout: function () {
this._affectedRange.addAll();
var layoutSite = this._createLayoutSite();
this._layout.initialize(layoutSite, this._groupsEnabled());
return this._layout.orientation === "horizontal";
},
_resetLayoutOrientation: function ListView_resetLayoutOrientation(resetScrollPosition) {
if (this._horizontalLayout) {
this._startProperty = "left";
this._scrollProperty = "scrollLeft";
this._scrollLength = "scrollWidth";
this._deleteWrapper.style.minHeight = "";
utilities.addClass(this._viewport, WinJS.UI._horizontalClass);
utilities.removeClass(this._viewport, WinJS.UI._verticalClass);
if (resetScrollPosition) {
this._viewport.scrollTop = 0;
}
} else {
this._startProperty = "top";
this._scrollProperty = "scrollTop";
this._scrollLength = "scrollHeight";
this._deleteWrapper.style.minWidth = "";
utilities.addClass(this._viewport, WinJS.UI._verticalClass);
utilities.removeClass(this._viewport, WinJS.UI._horizontalClass);
if (resetScrollPosition) {
this._viewport.scrollLeft = 0;
}
}
},
_resetLayout: function ListView_resetLayout() {
this._pendingLayoutReset = false;
this._affectedRange.addAll();
if (this._layout) {
this._layout.uninitialize();
this._horizontalLayout = this._initializeLayout();
this._resetLayoutOrientation();
}
},
_updateLayout: function ListView_updateLayout(layoutObject) {
var hadPreviousLayout = false;
if (this._layout) {
// The old layout is reset here in case it was in the middle of animating when the layout got changed. Reset
// will cancel out the animations.
this._cancelAsyncViewWork(true);
this._layout.uninitialize();
hadPreviousLayout = true;
}
var layoutImpl;
if (layoutObject && typeof layoutObject.type === "function") {
var LayoutCtor = requireSupportedForProcessing(layoutObject.type);
layoutImpl = new LayoutCtor(layoutObject);
} else if (layoutObject && (layoutObject.initialize)) {
layoutImpl = layoutObject;
} else {
layoutImpl = new WinJS.UI.GridLayout(layoutObject);
}
hadPreviousLayout && this._resetCanvas();
this._layoutImpl = layoutImpl;
this._layout = new WinJS.UI._LayoutWrapper(layoutImpl);
hadPreviousLayout && this._unsetFocusOnItem();
this._setFocusOnItem({ type: WinJS.UI.ObjectType.item, index: 0 });
this._selection._setFocused({ type: WinJS.UI.ObjectType.item, index: 0 });
this._horizontalLayout = this._initializeLayout();
this._resetLayoutOrientation(hadPreviousLayout);
if (hadPreviousLayout) {
this._canvas.style.width = this._canvas.style.height = "";
}
},
_currentMode: function ListView_currentMode() {
return this._mode;
},
_setSwipeClass: function ListView_setSwipeClass() {
// We apply an -ms-touch-action style to block panning and swiping from occurring at the same time. It is
// possible to pan in the margins between items and on lists without the swipe ability.
if ((this._currentMode() instanceof WinJS.UI._SelectionMode && this._selectionAllowed() && this._swipeBehavior === WinJS.UI.SwipeBehavior.select) ||
this._dragSource || this._reorderable) {
this._swipeable = true;
utilities.addClass(this._element, WinJS.UI._swipeableClass);
} else {
this._swipeable = false;
utilities.removeClass(this._element, WinJS.UI._swipeableClass);
}
var dragEnabled = (this.itemsDraggable || this.itemsReorderable),
swipeSelectEnabled = (this._selectionAllowed() && this._swipeBehavior === WinJS.UI.SwipeBehavior.select),
swipeEnabled = this._swipeable;
this._view.items.each(function (index, item, itemData) {
if (itemData.itemBox) {
var dragDisabledOnItem = utilities.hasClass(item, WinJS.UI._nonDraggableClass),
selectionDisabledOnItem = utilities.hasClass(item, WinJS.UI._nonSelectableClass),
nonSwipeable = utilities.hasClass(itemData.itemBox, WinJS.UI._nonSwipeableClass);
itemData.itemBox.draggable = (dragEnabled && !dragDisabledOnItem);
if (!swipeEnabled && nonSwipeable) {
utilities.removeClass(itemData.itemBox, WinJS.UI._nonSwipeableClass);
} else if (swipeEnabled) {
var makeNonSwipeable = (dragEnabled && !swipeSelectEnabled && dragDisabledOnItem) ||
(swipeSelectEnabled && !dragEnabled && selectionDisabledOnItem) ||
(dragDisabledOnItem && selectionDisabledOnItem);
if (makeNonSwipeable && !nonSwipeable) {
utilities.addClass(itemData.itemBox, WinJS.UI._nonSwipeableClass);
} else if (!makeNonSwipeable && nonSwipeable) {
utilities.removeClass(itemData.itemBox, WinJS.UI._nonSwipeableClass);
}
}
}
});
},
_resizeViewport: function ListView_resizeViewport() {
this._viewportWidth = WinJS.UI._UNINITIALIZED;
this._viewportHeight = WinJS.UI._UNINITIALIZED;
},
_onMSElementResize: function ListView_onResize() {
this._writeProfilerMark("_onMSElementResize,info");
Scheduler.schedule(function () {
if (this._isZombie()) { return; }
// If these values are uninitialized there is already a realization pass pending.
if (this._viewportWidth !== WinJS.UI._UNINITIALIZED && this._viewportHeight !== WinJS.UI._UNINITIALIZED) {
var newWidth = this._element.offsetWidth,
newHeight = this._element.offsetHeight;
if ((this._previousWidth !== newWidth) || (this._previousHeight !== newHeight)) {
this._writeProfilerMark("resize (" + this._previousWidth + "x" + this._previousHeight + ") => (" + newWidth + "x" + newHeight + "),info");
this._previousWidth = newWidth;
this._previousHeight = newHeight;
this._resizeViewport();
var that = this;
this._affectedRange.addAll();
this._batchViewUpdates(ViewChange.relayout, ScrollToPriority.low, function () {
return {
position: that.scrollPosition,
direction: "right"
};
});
}
}
}, Scheduler.Priority.max, this, "WinJS.UI.ListView._onMSElementResize");
},
_onFocus: function ListView_onFocus(event) {
this._hasKeyboardFocus = true;
var that = this;
function moveFocusToItem(keyboardFocused) {
that._changeFocus(that._selection._getFocused(), true, false, false, keyboardFocused);
}
// The keyboardEventsHelper object can get focus through three ways: We give it focus explicitly, in which case _shouldHaveFocus will be true,
// or the item that should be focused isn't in the viewport, so keyboard focus could only go to our helper. The third way happens when
// focus was already on the keyboard helper and someone alt tabbed away from and eventually back to the app. In the second case, we want to navigate
// back to the focused item via changeFocus(). In the third case, we don't want to move focus to a real item. We differentiate between cases two and three
// by checking if the flag _keyboardFocusInbound is true. It'll be set to true when the tab manager notifies us about the user pressing tab
// to move focus into the listview.
if (event.srcElement === this._keyboardEventsHelper) {
if (!this._keyboardEventsHelper._shouldHaveFocus && this._keyboardFocusInbound) {
moveFocusToItem(true);
} else {
this._keyboardEventsHelper._shouldHaveFocus = false;
}
} else if (event.srcElement === this._element) {
// If someone explicitly calls .focus() on the listview element, we need to route focus to the item that should be focused
moveFocusToItem();
} else {
if (this._mode.inboundFocusHandled) {
this._mode.inboundFocusHandled = false;
return
};
this._tabEventsHelper.childFocus = null;
// In the event that .focus() is explicitly called on an element, we need to figure out what item got focus and set our state appropriately.
var items = this._view.items,
entity = {},
element = this._groups.headerFrom(event.srcElement),
winItem = null;
if (element) {
entity.type = WinJS.UI.ObjectType.groupHeader;
entity.index = this._groups.index(element);
} else {
entity.index = items.index(event.srcElement);
entity.type = WinJS.UI.ObjectType.item;
element = items.itemBoxAt(entity.index);
winItem = items.itemAt(entity.index);
}
// In the old layouts, index will be -1 if a group header got focus
if (entity.index !== WinJS.UI._INVALID_INDEX) {
if (this._keyboardFocusInbound || this._selection._keyboardFocused()) {
if ((entity.type === WinJS.UI.ObjectType.groupHeader && event.srcElement === element) ||
(entity.type === WinJS.UI.ObjectType.item && event.srcElement.parentNode === element)) {
// For items we check the parentNode because the srcElement is win-item and element is win-itembox,
// for header, they should both be the win-groupheader
this._drawFocusRectangle(element);
}
}
if (this._tabManager.childFocus !== element && this._tabManager.childFocus !== winItem) {
//#DBG _ASSERT(entity.index !== WinJS.UI._INVALID_INDEX);
this._selection._setFocused(entity, this._keyboardFocusInbound || this._selection._keyboardFocused());
this._keyboardFocusInbound = false;
element = entity.type === WinJS.UI.ObjectType.groupHeader ? element : items.itemAt(entity.index);
this._tabManager.childFocus = element;
if (that._updater) {
var elementInfo = that._updater.elements[element.uniqueID],
focusIndex = entity.index;
if (elementInfo && elementInfo.newIndex) {
focusIndex = elementInfo.newIndex;
}
// Note to not set old and new focus to the same object
that._updater.oldFocus = { type: entity.type, index: focusIndex };
that._updater.newFocus = { type: entity.type, index: focusIndex };
}
}
}
}
},
_onBlur: function ListView_onBlur(event) {
this._hasKeyboardFocus = false;
this._itemFocused = false;
var element = this._view.items.itemBoxFrom(event.srcElement) || this._groups.headerFrom(event.srcElement);
if (element) {
this._clearFocusRectangle(element);
}
if (this._focusRequest) {
// If we're losing focus and we had an outstanding focus request, that means the focused item isn't realized. To enable the user to tab back
// into the listview, we'll make the keyboardEventsHelper tabbable for this scenario.
this._tabEventsHelper.childFocus = this._keyboardEventsHelper;
}
},
_onMSManipulationStateChanged: function ListView_onMSManipulationStateChanged(ev) {
var that = this;
function done() {
that._manipulationEndSignal = null;
}
this._manipulationState = ev.currentState;
that._writeProfilerMark("_onMSManipulationStateChanged state(" + ev.currentState + "),info");
if (this._manipulationState !== MSManipulationEvent.MS_MANIPULATION_STATE_STOPPED && !this._manipulationEndSignal) {
this._manipulationEndSignal = new WinJS._Signal();
this._manipulationEndSignal.promise.done(done, done);
}
if (this._manipulationState === MSManipulationEvent.MS_MANIPULATION_STATE_STOPPED) {
this._manipulationEndSignal.complete();
}
},
_pendingScroll: false,
_onScroll: function ListView_onScroll() {
if (!this._zooming && !this._pendingScroll) {
this._checkScroller();
}
},
_checkScroller: function ListView_checkScroller() {
if (this._isZombie()) { return; }
var currentScrollPosition = this._viewportScrollPosition;
if (currentScrollPosition !== this._lastScrollPosition) {
this._pendingScroll = requestAnimationFrame(this._checkScroller.bind(this));
var direction = (currentScrollPosition < this._lastScrollPosition) ? "left" : "right";
currentScrollPosition = Math.max(0, currentScrollPosition);
this._lastScrollPosition = currentScrollPosition;
this._raiseViewLoading(true);
var that = this;
this._view.onScroll(function () {
return {
position: that._lastScrollPosition,
direction: direction
};
},
this._manipulationEndSignal ? this._manipulationEndSignal.promise : Promise.timeout(WinJS.UI._DEFERRED_SCROLL_END));
} else {
this._pendingScroll = null;
}
},
_onTabEnter: function ListView_onTabEnter() {
this._keyboardFocusInbound = true;
},
_onTabExit: function ListView_onTabExit() {
this._keyboardFocusInbound = false;
},
_onPropertyChange: function ListView_onPropertyChange(list) {
var that = this;
list.forEach(function (record) {
var dirChanged = false;
if (record.attributeName === "dir") {
dirChanged = true;
} else if (record.attributeName === "style") {
dirChanged = (that._cachedStyleDir != record.target.style.direction);
}
if (dirChanged) {
that._cachedStyleDir = record.target.style.direction;
that._cachedRTL = null;
utilities[that._rtl() ? "addClass" : "removeClass"](that._element, WinJS.UI._rtlListViewClass);
that._lastScrollPosition = 0;
that._viewportScrollPosition = 0;
that.forceLayout();
}
if (record.attributeName === "tabIndex") {
var newTabIndex = that._element.tabIndex;
if (newTabIndex >= 0) {
that._view.items.each(function (index, item, itemData) {
item.tabIndex = newTabIndex;
});
that._tabIndex = newTabIndex;
that._tabManager.tabIndex = newTabIndex;
that._tabEventsHelper.tabIndex = newTabIndex;
that._element.tabIndex = -1;
}
}
});
},
_getCanvasMargins: function ListView_getCanvasMargins() {
if (!this._canvasMargins) {
this._canvasMargins = WinJS.UI._getMargins(this._canvas);
}
return this._canvasMargins;
},
// Convert between canvas coordinates and viewport coordinates
_convertCoordinatesByCanvasMargins: function ListView_convertCoordinatesByCanvasMargins(coordinates, conversionCallback) {
function fix(field, offset) {
if (coordinates[field] !== undefined) {
coordinates[field] = conversionCallback(coordinates[field], offset);
}
}
var offset;
if (this._horizontal()) {
offset = this._getCanvasMargins()[this._rtl() ? "right" : "left"];
fix("left", offset);
} else {
offset = this._getCanvasMargins().top;
fix("top", offset);
}
fix("begin", offset);
fix("end", offset);
return coordinates;
},
_convertFromCanvasCoordinates: function ListView_convertFromCanvasCoordinates(coordinates) {
return this._convertCoordinatesByCanvasMargins(coordinates, function (coordinate, canvasMargin) {
return coordinate + canvasMargin;
});
},
_convertToCanvasCoordinates: function ListView_convertToCanvasCoordinates(coordinates) {
return this._convertCoordinatesByCanvasMargins(coordinates, function (coordinate, canvasMargin) {
return coordinate - canvasMargin;
});
},
// Methods in the site interface used by ScrollView
_getViewportSize: function ListView_getViewportSize() {
if (this._viewportWidth === WinJS.UI._UNINITIALIZED || this._viewportHeight === WinJS.UI._UNINITIALIZED) {
this._viewportWidth = Math.max(0, utilities.getContentWidth(this._element));
this._viewportHeight = Math.max(0, utilities.getContentHeight(this._element));
this._writeProfilerMark("viewportSizeDetected width:" + this._viewportWidth + " height:" + this._viewportHeight);
this._previousWidth = this._element.offsetWidth;
this._previousHeight = this._element.offsetHeight;
}
return {
width: this._viewportWidth,
height: this._viewportHeight
};
},
_itemsCount: function ListView_itemsCount() {
var that = this;
function cleanUp() {
that._itemsCountPromise = null;
}
if (this._cachedCount !== WinJS.UI._UNINITIALIZED) {
return Promise.wrap(this._cachedCount);
} else {
var retVal;
if (!this._itemsCountPromise) {
retVal = this._itemsCountPromise = this._itemsManager.dataSource.getCount().then(
function (count) {
if (count === thisWinUI.CountResult.unknown) {
count = 0;
}
that._cachedCount = count;
that._selection._updateCount(that._cachedCount);
return count;
},
function () {
return WinJS.Promise.cancel;
}
);
this._itemsCountPromise.then(cleanUp, cleanUp);
} else {
retVal = this._itemsCountPromise;
}
return retVal;
}
},
_isSelected: function ListView_isSelected(index) {
return this._selection._isIncluded(index);
},
_LoadingState: {
itemsLoading: "itemsLoading",
viewPortLoaded: "viewPortLoaded",
itemsLoaded: "itemsLoaded",
complete: "complete"
},
_raiseViewLoading: function ListView_raiseViewLoading(scrolling) {
if (this._loadingState !== this._LoadingState.itemsLoading) {
this._scrolling = !!scrolling;
}
this._setViewState(this._LoadingState.itemsLoading);
},
_raiseViewComplete: function ListView_raiseViewComplete() {
if (!this._disposed && !this._view.animating) {
this._setViewState(this._LoadingState.complete);
}
},
_setViewState: function ListView_setViewState(state) {
if (state !== this._loadingState) {
var detail = null;
// We can go from any state to itemsLoading but the rest of the states transitions must follow this
// order: itemsLoading -> viewPortLoaded -> itemsLoaded -> complete.
// Recursively set the previous state until you hit the current state or itemsLoading.
switch (state) {
case this._LoadingState.viewPortLoaded:
if (!this._scheduledForDispose) {
scheduleForDispose(this);
this._scheduledForDispose = true;
}
this._setViewState(this._LoadingState.itemsLoading);
break;
case this._LoadingState.itemsLoaded:
detail = {
scrolling: this._scrolling
};
this._setViewState(this._LoadingState.viewPortLoaded);
break;
case this._LoadingState.complete:
this._setViewState(this._LoadingState.itemsLoaded);
this._updateDeleteWrapperSize(true);
break;
}
this._writeProfilerMark("loadingStateChanged:" + state + ",info");
this._loadingState = state;
var eventObject = document.createEvent("CustomEvent");
eventObject.initCustomEvent("loadingstatechanged", true, false, detail);
this._element.dispatchEvent(eventObject);
}
},
_createTemplates: function ListView_createTemplates() {
function createNodeWithClass(className, skipAriaHidden) {
var element = document.createElement("div");
element.className = className;
if (!skipAriaHidden) {
element.setAttribute("aria-hidden", true);
}
return element;
}
this._itemBoxTemplate = createNodeWithClass(WinJS.UI._itemBoxClass, true);
},
// Methods used by SelectionManager
_updateSelection: function ListView_updateSelection() {
var indices = this._selection.getIndices(),
selectAll = this._selection.isEverything(),
selectionMap = {};
if (!selectAll) {
for (var i = 0, len = indices.length ; i < len; i++) {
var index = indices[i];
selectionMap[index] = true;
}
}
var that = this;
this._view.items.each(function (index, element, itemData) {
if (itemData.itemBox && !utilities.hasClass(itemData.itemBox, thisWinUI._swipeClass)) {
var selected = selectAll || !!selectionMap[index];
WinJS.UI._ItemEventsHandler.renderSelection(itemData.itemBox, element, selected, true);
if (itemData.container) {
utilities[selected ? "addClass" : "removeClass"](itemData.container, WinJS.UI._selectedClass);
}
}
});
},
_getViewportLength: function ListView_getViewportLength() {
return this._getViewportSize()[this._horizontal() ? "width" : "height"];
},
_horizontal: function ListView_horizontal() {
return this._horizontalLayout;
},
_rtl: function ListView_rtl() {
if (typeof this._cachedRTL !== "boolean") {
this._cachedRTL = window.getComputedStyle(this._element, null).direction === "rtl";
}
return this._cachedRTL;
},
_showProgressBar: function ListView_showProgressBar(parent, x, y) {
var progressBar = this._progressBar,
progressStyle = progressBar.style;
if (!progressBar.parentNode) {
this._fadingProgressBar = false;
if (this._progressIndicatorDelayTimer) {
this._progressIndicatorDelayTimer.cancel();
}
var that = this;
this._progressIndicatorDelayTimer = Promise.timeout(WinJS.UI._LISTVIEW_PROGRESS_DELAY).then(function () {
if (!that._isZombie()) {
parent.appendChild(progressBar);
AnimationHelper.fadeInElement(progressBar);
that._progressIndicatorDelayTimer = null;
}
});
}
progressStyle[this._rtl() ? "right" : "left"] = x;
progressStyle.top = y;
},
_hideProgressBar: function ListView_hideProgressBar() {
if (this._progressIndicatorDelayTimer) {
this._progressIndicatorDelayTimer.cancel();
this._progressIndicatorDelayTimer = null;
}
var progressBar = this._progressBar;
if (progressBar.parentNode && !this._fadingProgressBar) {
this._fadingProgressBar = true;
var that = this;
AnimationHelper.fadeOutElement(progressBar).then(function () {
if (progressBar.parentNode) {
progressBar.parentNode.removeChild(progressBar);
}
that._fadingProgressBar = false;
});
}
},
_getPanAxis: function () {
return this._horizontal() ? "horizontal" : "vertical";
},
_configureForZoom: function (isZoomedOut, isCurrentView, triggerZoom, pagesToPrefetch) {
if (WinJS.validation) {
if (!this._view.realizePage || typeof this._view.begin !== "number") {
throw new WinJS.ErrorFromName("WinJS.UI.ListView.NotCompatibleWithSemanticZoom", strings.notCompatibleWithSemanticZoom);
}
}
this._isZoomedOut = isZoomedOut;
this._disableEntranceAnimation = !isCurrentView;
this._isCurrentZoomView = isCurrentView;
this._triggerZoom = triggerZoom;
},
_setCurrentItem: function (x, y) {
// First, convert the position into canvas coordinates
if (this._rtl()) {
x = this._viewportWidth - x;
}
if (this._horizontal()) {
x += this.scrollPosition;
} else {
y += this.scrollPosition;
}
var result = this._view.hitTest(x, y),
entity = { type: result.type ? result.type : WinJS.UI.ObjectType.item, index: result.index };
if (entity.index >= 0) {
if (this._hasKeyboardFocus) {
this._changeFocus(entity, true, false, true);
} else {
this._changeFocusPassively(entity);
}
}
},
_getCurrentItem: function () {
var focused = this._selection._getFocused();
if (focused.type === WinJS.UI.ObjectType.groupHeader) {
focused = { type: WinJS.UI.ObjectType.item, index: this._groups.group(focused.index).startIndex };
}
if (typeof focused.index !== "number") {
// Do a hit-test in the viewport center
this._setCurrentItem(0.5 * this._viewportWidth, 0.5 * this._viewportHeight);
focused = this._selection._getFocused();
}
var that = this;
var promisePosition = this._getItemOffsetPosition(focused.index).
then(function (posCanvas) {
var scrollOffset = that._canvasStart;
posCanvas[that._startProperty] += scrollOffset;
return posCanvas;
});
return Promise.join({
item: this._dataSource.itemFromIndex(focused.index),
position: promisePosition
});
},
_beginZoom: function () {
this._zooming = true;
// Hide the scrollbar and extend the content beyond the ListView viewport
var horizontal = this._horizontal(),
scrollOffset = -this.scrollPosition;
utilities.addClass(this._viewport, horizontal ? WinJS.UI._zoomingXClass : WinJS.UI._zoomingYClass);
this._canvasStart = scrollOffset;
utilities.addClass(this._viewport, horizontal ? WinJS.UI._zoomingYClass : WinJS.UI._zoomingXClass);
},
_positionItem: function (item, position) {
var that = this;
function positionItemAtIndex(index) {
return that._getItemOffsetPosition(index).then(function positionItemAtIndex_then_ItemOffsetPosition(posCanvas) {
var horizontal = that._horizontal(),
canvasMargins = that._getCanvasMargins(),
canvasSize = that._canvas[horizontal ? "offsetWidth" : "offsetHeight"] + canvasMargins[(horizontal ? (that._rtl() ? "right" : "left") : "top")],
viewportSize = (horizontal ? that._viewportWidth : that._viewportHeight),
scrollPosition;
// Align the leading edge
var start = position[that._startProperty],
startMax = viewportSize - (horizontal ? posCanvas.width : posCanvas.height);
// Ensure the item ends up within the viewport
start = Math.max(0, Math.min(startMax, start));
scrollPosition = posCanvas[that._startProperty] - start;
// Ensure the scroll position is valid
var adjustedScrollPosition = Math.max(0, Math.min(canvasSize - viewportSize, scrollPosition)),
scrollAdjustment = adjustedScrollPosition - scrollPosition;
scrollPosition = adjustedScrollPosition;
// Since a zoom is in progress, adjust the div position
var scrollOffset = -scrollPosition;
that._canvasStart = scrollOffset;
var entity = { type: WinJS.UI.ObjectType.item, index: index }
if (that._hasKeyboardFocus) {
that._changeFocus(entity, true);
} else {
that._changeFocusPassively(entity);
}
that._raiseViewLoading(true);
that._view.realizePage(scrollPosition, true);
return (
horizontal ?
{ x: scrollAdjustment, y: 0 } :
{ x: 0, y: scrollAdjustment }
);
});
}
var itemIndex = 0;
if (item) {
itemIndex = (this._isZoomedOut ? item.groupIndexHint : item.firstItemIndexHint);
}
if (typeof itemIndex === "number") {
return positionItemAtIndex(itemIndex);
} else {
// We'll need to obtain the index from the data source
var itemPromise;
var key = (this._isZoomedOut ? item.groupKey : item.firstItemKey);
if (typeof key === "string" && this._dataSource.itemFromKey) {
itemPromise = this._dataSource.itemFromKey(key, (this._isZoomedOut ? {
groupMemberKey: item.key,
groupMemberIndex: item.index
} : null));
} else {
var description = (this._isZoomedOut ? item.groupDescription : item.firstItemDescription);
if (WinJS.validation) {
if (description === undefined) {
throw new WinJS.ErrorFromName("WinJS.UI.ListView.InvalidItem", strings.listViewInvalidItem);
}
}
itemPromise = this._dataSource.itemFromDescription(description);
}
return itemPromise.then(function (item) {
return positionItemAtIndex(item.index);
});
}
},
_endZoom: function (isCurrentView) {
if (this._isZombie()) {
return;
}
// Crop the content again and re-enable the scrollbar
var horizontal = this._horizontal(),
scrollOffset = this._canvasStart;
utilities.removeClass(this._viewport, WinJS.UI._zoomingYClass);
utilities.removeClass(this._viewport, WinJS.UI._zoomingXClass);
this._canvasStart = 0;
this._viewportScrollPosition = -scrollOffset;
this._disableEntranceAnimation = !isCurrentView;
this._isCurrentZoomView = isCurrentView;
this._zooming = false;
this._view.realizePage(this.scrollPosition, false);
},
_getItemOffsetPosition: function (index) {
var that = this;
return this._getItemOffset({ type: WinJS.UI.ObjectType.item, index: index }).then(function (position) {
return that._ensureFirstColumnRange(WinJS.UI.ObjectType.item).then(function () {
position = that._correctRangeInFirstColumn(position, WinJS.UI.ObjectType.item);
position = that._convertFromCanvasCoordinates(position);
if (that._horizontal()) {
position.left = position.begin;
position.width = position.end - position.begin;
position.height = position.totalHeight;
} else {
position.top = position.begin;
position.height = position.end - position.begin;
position.width = position.totalWidth;
}
return position;
});
});
},
_groupRemoved: function (key) {
this._groupFocusCache.deleteGroup(key);
},
_updateFocusCache: function (itemIndex) {
if (this._updateFocusCacheItemRequest) {
this._updateFocusCacheItemRequest.cancel();
}
var that = this;
this._updateFocusCacheItemRequest = this._view.items.requestItem(itemIndex).then(function (item) {
that._updateFocusCacheItemRequest = null;
var itemData = that._view.items.itemDataAt(itemIndex);
var groupIndex = that._groups.groupFromItem(itemIndex);
var groupKey = that._groups.group(groupIndex).key;
if (itemData.itemsManagerRecord.item) {
that._groupFocusCache.updateCache(groupKey, itemData.itemsManagerRecord.item.key, itemIndex);
}
});
},
_changeFocus: function (newFocus, skipSelection, ctrlKeyDown, skipEnsureVisible, keyboardFocused) {
//#DBG _ASSERT(newFocus.index !== -1);
if (this._isZombie()) {
return;
}
var targetItem;
if (newFocus.type !== WinJS.UI.ObjectType.groupHeader) {
targetItem = this._view.items.itemAt(newFocus.index);
if (!skipSelection && targetItem && utilities.hasClass(targetItem, WinJS.UI._nonSelectableClass)) {
skipSelection = true;
}
this._updateFocusCache(newFocus.index);
} else {
var group = this._groups.group(newFocus.index);
targetItem = group && group.header;
}
this._unsetFocusOnItem(!!targetItem);
this._hasKeyboardFocus = true;
this._selection._setFocused(newFocus, keyboardFocused);
if (!skipEnsureVisible) {
this.ensureVisible(newFocus);
}
// _selection.set() needs to know which item has focus so we
// must call it after _selection._setFocused() has been called.
if (!skipSelection && this._selectFocused(ctrlKeyDown)) {
this._selection.set(newFocus.index);
}
this._setFocusOnItem(newFocus);
},
// Updates ListView's internal focus state and, if ListView currently has focus, moves
// Trident's focus to the item at index newFocus.
// Similar to _changeFocus except _changeFocusPassively doesn't:
// - ensure the item is selected or visible
// - set Trident's focus to newFocus when ListView doesn't have focus
_changeFocusPassively: function (newFocus) {
//#DBG _ASSERT(newFocus.index !== -1);
var targetItem;
if (newFocus.type !== WinJS.UI.ObjectType.groupHeader) {
targetItem = this._view.items.itemAt(newFocus.index);
this._updateFocusCache(newFocus.index);
} else {
var group = this._groups.group(newFocus.index);
targetItem = group && group.header;
}
this._unsetFocusOnItem(!!targetItem);
this._selection._setFocused(newFocus);
this._setFocusOnItem(newFocus);
},
_drawFocusRectangle: function (item) {
if (WinJS.Utilities.hasClass(item, thisWinUI._headerClass)) {
WinJS.Utilities.addClass(item, thisWinUI._itemFocusClass);
} else {
var itemBox = this._view.items.itemBoxFrom(item);
//#DBG _ASSERT(utilities.hasClass(itemBox, WinJS.UI._itemBoxClass));
if (itemBox.querySelector("." + thisWinUI._itemFocusOutlineClass)) {
return;
}
utilities.addClass(itemBox, thisWinUI._itemFocusClass);
var outline = document.createElement("div");
outline.className = thisWinUI._itemFocusOutlineClass;
itemBox.appendChild(outline);
}
},
_clearFocusRectangle: function (item) {
if (!item || this._isZombie()) {
return;
}
var itemBox = this._view.items.itemBoxFrom(item);
if (itemBox) {
utilities.removeClass(itemBox, thisWinUI._itemFocusClass);
//#DBG _ASSERT(utilities.hasClass(itemBox, WinJS.UI._itemBoxClass));
var outline = itemBox.querySelector("." + thisWinUI._itemFocusOutlineClass);
if (outline) {
outline.parentNode.removeChild(outline);
}
} else {
var header = this._groups.headerFrom(item);
if (header) {
utilities.removeClass(header, thisWinUI._itemFocusClass);
}
}
},
_defaultInvoke: function (entity) {
if (this._isZoomedOut) {
this._changeFocusPassively(entity);
this._triggerZoom();
}
},
_selectionAllowed: function ListView_selectionAllowed(itemIndex) {
var item = (itemIndex !== undefined ? this.elementFromIndex(itemIndex) : null),
itemSelectable = !(item && utilities.hasClass(item, WinJS.UI._nonSelectableClass));
return itemSelectable && this._selectionMode !== WinJS.UI.SelectionMode.none;
},
_multiSelection: function ListView_multiSelection() {
return this._selectionMode === WinJS.UI.SelectionMode.multi;
},
_selectOnTap: function ListView_selectOnTap() {
return this._tap === WinJS.UI.TapBehavior.toggleSelect || this._tap === WinJS.UI.TapBehavior.directSelect;
},
_selectFocused: function ListView_selectFocused(ctrlKeyDown) {
return this._tap === WinJS.UI.TapBehavior.directSelect && this._selectionMode === WinJS.UI.SelectionMode.multi && !ctrlKeyDown;
},
_dispose: function () {
if (!this._disposed) {
this._disposed = true;
var clear = function clear(e) {
e && (e.innerText = "");
}
this._batchingViewUpdates && this._batchingViewUpdates.cancel();
this._view && this._view._dispose && this._view._dispose();
this._mode && this._mode._dispose && this._mode._dispose();
this._groups && this._groups._dispose && this._groups._dispose();
this._selection && this._selection._dispose && this._selection._dispose();
this._layout && this._layout.uninitialize && this._layout.uninitialize();
this._itemsCountPromise && this._itemsCountPromise.cancel();
this._versionManager && this._versionManager._dispose();
this._clearInsertedItems();
this._itemsManager && this._itemsManager.release();
clear(this._viewport);
clear(this._canvas);
clear(this._canvasProxy);
this._versionManager = null;
this._view = null;
this._mode = null;
this._element = null;
this._viewport = null;
this._itemsManager = null;
this._canvas = null;
this._canvasProxy = null;
this._itemsCountPromise = null;
this._scrollToFunctor = null;
var index = controlsToDispose.indexOf(this);
if (index >= 0) {
controlsToDispose.splice(index, 1);
}
}
},
_isZombie: function () {
// determines if this ListView is no longer in the DOM or has been cleared
//
return this._disposed || !(this.element.firstElementChild && document.body.contains(this.element));
},
_ifZombieDispose: function () {
var zombie = this._isZombie();
if (zombie && !this._disposed) {
scheduleForDispose(this);
}
return zombie;
},
_animationsDisabled: function () {
if (this._viewportWidth === 0 || this._viewportHeight === 0) {
return true;
}
return !WinJS.UI.isAnimationEnabled();
},
_fadeOutViewport: function ListView_fadeOutViewport() {
var that = this;
return new Promise(function (complete) {
if (that._animationsDisabled()) {
complete();
return;
}
if (!that._fadingViewportOut) {
if (that._waitingEntranceAnimationPromise) {
that._waitingEntranceAnimationPromise.cancel();
that._waitingEntranceAnimationPromise = null;
}
var eventDetails = that._fireAnimationEvent(WinJS.UI.ListViewAnimationType.contentTransition);
that._firedAnimationEvent = true;
if (!eventDetails.prevented) {
that._fadingViewportOut = true;
that._viewport.style["-ms-overflow-style"] = "none";
AnimationHelper.fadeOutElement(that._viewport).then(function () {
if (that._isZombie()) { return; }
that._fadingViewportOut = false;
that._viewport.style.opacity = 1.0;
complete();
});
} else {
that._disableEntranceAnimation = true;
that._viewport.style.opacity = 1.0;
complete();
}
}
});
},
_animateListEntrance: function (firstTime) {
var eventDetails = {
prevented: false,
animationPromise: Promise.wrap()
};
var that = this;
function resetViewOpacity() {
that._canvas.style.opacity = 1;
that._viewport.style["-ms-overflow-style"] = "";
}
if (this._disableEntranceAnimation || this._animationsDisabled()) {
resetViewOpacity();
if (this._waitingEntranceAnimationPromise) {
this._waitingEntranceAnimationPromise.cancel();
this._waitingEntranceAnimationPromise = null;
}
return Promise.wrap();
}
if (!this._firedAnimationEvent) {
eventDetails = this._fireAnimationEvent(WinJS.UI.ListViewAnimationType.entrance);
} else {
this._firedAnimationEvent = false;
}
if (eventDetails.prevented) {
resetViewOpacity();
return Promise.wrap();
} else {
if (this._waitingEntranceAnimationPromise) {
this._waitingEntranceAnimationPromise.cancel();
}
this._canvas.style.opacity = 0;
this._viewport.style["-ms-overflow-style"] = "none";
this._waitingEntranceAnimationPromise = eventDetails.animationPromise.then(function () {
if (!that._isZombie()) {
that._canvas.style.opacity = 1;
return AnimationHelper.animateEntrance(that._viewport, firstTime).then(function () {
if (!that._isZombie()) {
that._viewport.style["-ms-overflow-style"] = "";
that._waitingEntranceAnimationPromise = null;
}
});
}
});
return this._waitingEntranceAnimationPromise;
}
},
_fireAnimationEvent: function (type) {
var animationEvent = document.createEvent("CustomEvent"),
animationPromise = Promise.wrap();
animationEvent.initCustomEvent("contentanimating", true, true, {
type: type
});
if (type === WinJS.UI.ListViewAnimationType.entrance) {
animationEvent.detail.setPromise = function (delayPromise) {
animationPromise = delayPromise;
};
}
var prevented = !this._element.dispatchEvent(animationEvent);
return {
prevented: prevented,
animationPromise: animationPromise
}
},
// If they don't yet exist, create the start and end markers which are required
// by Narrator's aria-flowto/flowfrom implementation. They mark the start and end
// of ListView's set of out-of-order DOM elements and so they must surround the
// headers and groups in the DOM.
_createAriaMarkers: function ListView_createAriaMarkers() {
if (!this._viewport.getAttribute("aria-label")) {
this._viewport.setAttribute("aria-label", strings.listViewViewportAriaLabel);
}
if (!this._ariaStartMarker) {
this._ariaStartMarker = document.createElement("div");
this._ariaStartMarker.id = this._ariaStartMarker.uniqueID;
this._viewport.insertBefore(this._ariaStartMarker, this._viewport.firstElementChild);
}
if (!this._ariaEndMarker) {
this._ariaEndMarker = document.createElement("div");
this._ariaEndMarker.id = this._ariaEndMarker.uniqueID;
this._viewport.appendChild(this._ariaEndMarker);
}
},
// If the ListView is in static mode, then the roles of the list and items should be "list" and "listitem", respectively.
// Otherwise, the roles should be "listbox" and "option." If the ARIA roles are out of sync with the ListView's
// static/interactive state, update the role of the ListView and the role of each realized item.
_updateItemsAriaRoles: function ListView_updateItemsAriaRoles() {
var that = this;
var listRole = this._element.getAttribute("role"),
expectedListRole,
expectedItemRole;
if (this._currentMode().staticMode()) {
expectedListRole = "list";
expectedItemRole = "listitem";
} else {
expectedListRole = "listbox";
expectedItemRole = "option";
}
if (listRole !== expectedListRole || this._itemRole !== expectedItemRole) {
this._element.setAttribute("role", expectedListRole);
this._itemRole = expectedItemRole;
this._view.items.each(function (index, itemElement, itemData) {
itemElement.setAttribute("role", that._itemRole);
});
}
},
_updateGroupHeadersAriaRoles: function ListView_updateGroupHeadersAriaRoles() {
var that = this,
headerRole = (this.groupHeaderTapBehavior === WinJS.UI.GroupHeaderTapBehavior.none ? "separator" : "link");
if (this._headerRole !== headerRole) {
this._headerRole = headerRole;
for (var i = 0, len = this._groups.length() ; i < len; i++) {
var header = this._groups.group(i).header;
if (header) {
header.setAttribute("role", this._headerRole);
}
}
}
},
// Avoids unnecessary UIA selection events by only updating aria-selected if it has changed
_setAriaSelected: function ListView_setAriaSelected(itemElement, isSelected) {
var ariaSelected = (itemElement.getAttribute("aria-selected") === "true");
if (isSelected !== ariaSelected) {
itemElement.setAttribute("aria-selected", isSelected);
}
},
_setupAriaSelectionObserver: function ListView_setupAriaSelectionObserver(item) {
if (!item._mutationObserver) {
this._mutationObserver.observe(item, { attributes: true, attributeFilter: ["aria-selected"] });
item._mutationObserver = true;
}
},
_itemPropertyChange: function ListView_itemPropertyChange(list) {
if (this._isZombie()) { return; }
var that = this;
var singleSelection = that._selectionMode === WinJS.UI.SelectionMode.single;
var changedItems = [];
var unselectableItems = [];
function revertAriaSelected(items) {
items.forEach(function (entry) {
entry.item.setAttribute("aria-selected", !entry.selected);
});
}
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i].target;
var itemBox = that._view.items.itemBoxFrom(item);
var selected = item.getAttribute("aria-selected") === "true";
// Only respond to aria-selected changes coming from UIA. This check
// relies on the fact that, in renderSelection, we update the selection
// visual before aria-selected.
if (itemBox && (selected !== WinJS.UI._isSelectionRendered(itemBox))) {
var index = that._view.items.index(itemBox);
var entry = { index: index, item: item, selected: selected };
(that._selectionAllowed(index) ? changedItems : unselectableItems).push(entry);
}
}
if (changedItems.length > 0) {
var signal = new WinJS._Signal();
that.selection._synchronize(signal).then(function () {
var newSelection = that.selection._cloneSelection();
changedItems.forEach(function (entry) {
if (entry.selected) {
newSelection[singleSelection ? "set" : "add"](entry.index);
} else {
newSelection.remove(entry.index);
}
});
return that.selection._set(newSelection);
}).then(function (approved) {
if (!that._isZombie() && !approved) {
// A selectionchanging event handler rejected the selection change
revertAriaSelected(changedItems);
}
signal.complete();
});
}
revertAriaSelected(unselectableItems);
},
_groupsEnabled: function () {
return !!this._groups.groupDataSource;
},
_getItemPosition: function ListView_getItemPosition(entity, preserveItemsBlocks) {
var that = this;
return this._view.waitForEntityPosition(entity).then(function () {
var container = (entity.type === WinJS.UI.ObjectType.groupHeader ? that._view._getHeaderContainer(entity.index) : that._view.getContainer(entity.index));
if (container) {
that._writeProfilerMark("WinJS.UI.ListView:getItemPosition,info");
if (that._view._expandedRange) {
var itemsBlockFrom = that._view._expandedRange.first.index,
itemsBlockTo = that._view._expandedRange.last.index;
} else {
preserveItemsBlocks = false;
}
if (entity.type === WinJS.UI.ObjectType.item) {
preserveItemsBlocks = !!preserveItemsBlocks;
preserveItemsBlocks &= that._view._ensureContainerInDOM(entity.index);
} else {
preserveItemsBlocks = false;
}
var margins = that._getItemMargins(entity.type),
position = {
left: (that._rtl() ? getOffsetRight(container) - margins.right : container.offsetLeft - margins.left),
top: container.offsetTop - margins.top,
totalWidth: utilities.getTotalWidth(container),
totalHeight: utilities.getTotalHeight(container),
contentWidth: utilities.getContentWidth(container),
contentHeight: utilities.getContentHeight(container)
};
if (preserveItemsBlocks) {
that._view._forceItemsBlocksInDOM(itemsBlockFrom, itemsBlockTo + 1);
}
// When a translation is applied to the surface during zooming, offsetLeft includes the canvas margins, so the left/top position will already be in canvas coordinates.
// If we're not zooming, we need to convert the position to canvas coordinates before returning.
return (that._zooming && that._canvasStart !== 0 ? position : that._convertToCanvasCoordinates(position));
} else {
return WinJS.Promise.cancel;
}
});
},
_getItemOffset: function ListView_getItemOffset(entity, preserveItemsBlocks) {
var that = this;
return this._getItemPosition(entity, preserveItemsBlocks).then(function (pos) {
// _getItemOffset also includes the right/bottom margin of the previous row/column of items, so that ensureVisible/indexOfFirstVisible will jump such that
// the previous row/column is directly offscreen of the target item.
var margins = that._getItemMargins(entity.type);
if (that._horizontal()) {
var rtl = that._rtl();
pos.begin = pos.left - margins[rtl ? "left" : "right"],
pos.end = pos.left + pos.totalWidth + margins[rtl ? "right" : "left"]
} else {
pos.begin = pos.top - margins.bottom,
pos.end = pos.top + pos.totalHeight + margins.top
}
return pos;
});
},
_getItemMargins: function ListView_getItemMargins(type) {
type = type || WinJS.UI.ObjectType.item;
var that = this;
var calculateMargins = function (className) {
var item = that._canvas.querySelector("." + className),
cleanup;
if (!item) {
item = document.createElement("div"),
utilities.addClass(item, className);
that._viewport.appendChild(item);
cleanup = true;
}
var margins = WinJS.UI._getMargins(item);
if (cleanup) {
that._viewport.removeChild(item);
}
return margins;
};
if (type !== WinJS.UI.ObjectType.groupHeader) {
return (this._itemMargins ? this._itemMargins : (this._itemMargins = calculateMargins(WinJS.UI._containerClass)));
} else {
return (this._headerMargins ? this._headerMargins : (this._headerMargins = calculateMargins(WinJS.UI._headerContainerClass)));
}
},
_fireAccessibilityAnnotationCompleteEvent: function ListView_fireAccessibilityAnnotationCompleteEvent(firstIndex, lastIndex, firstHeaderIndex, lastHeaderIndex) {
// This event is fired in these cases:
// - When the data source count is 0, it is fired after the aria markers have been
// updated. The event detail will be { firstIndex: -1, lastIndex: -1 }.
// - When the data source count is non-zero, it is fired after the aria markers
// have been updated and the deferred work for the aria properties on the items
// has completed.
// - When an item gets focus. The event will be { firstIndex: indexOfItem, lastIndex: indexOfItem }.
var detail = {
firstIndex: firstIndex,
lastIndex: lastIndex,
firstHeaderIndex: (+firstHeaderIndex) || -1,
lastHeaderIndex: (+lastHeaderIndex) || -1
}
var eventObject = document.createEvent("CustomEvent");
eventObject.initCustomEvent("accessibilityannotationcomplete", true, false, detail);
this._element.dispatchEvent(eventObject);
},
_ensureFirstColumnRange: function ListView_ensureFirstColumnRange(type) {
var propName = (type === WinJS.UI.ObjectType.item ? "_firstItemRange" : "_firstHeaderRange");
if (!this[propName]) {
var that = this;
return this._getItemOffset({ type: type, index: 0 }, true).then(function (firstRange) {
that[propName] = firstRange;
});
} else {
return Promise.wrap();
}
},
_correctRangeInFirstColumn: function ListView_correctRangeInFirstColumn(range, type) {
var firstRange = (type === WinJS.UI.ObjectType.groupHeader ? this._firstHeaderRange : this._firstItemRange);
if (firstRange.begin === range.begin) {
if (this._horizontal()) {
range.begin = -this._getCanvasMargins()[this._rtl() ? "right" : "left"];
} else {
range.begin = -this._getCanvasMargins().top;
}
}
return range;
},
_updateContainers: function ListView_updateContainers(groups, count, containersDelta, modifiedElements) {
var that = this;
var maxContainers = this._view.containers.length + (containersDelta > 0 ? containersDelta : 0);
var newTree = [];
var newKeyToGroupIndex = {};
var newContainers = [];
var removedContainers = [];
function createContainer() {
var element = document.createElement("div");
element.className = WinJS.UI._containerClass;
return element;
}
function updateExistingGroupWithBlocks(groupNode, firstItem, newSize) {
if (firstItem + newSize > maxContainers) {
newSize = maxContainers - firstItem;
}
var itemsContainer = groupNode.itemsContainer,
blocks = itemsContainer.itemsBlocks,
lastBlock = blocks.length ? blocks[blocks.length - 1] : null,
currentSize = blocks.length ? (blocks.length - 1) * that._view._blockSize + lastBlock.items.length : 0,
delta = newSize - currentSize,
oldSize, children;
if (delta > 0) {
if (lastBlock && lastBlock.items.length < that._view._blockSize) {
var toAdd = Math.min(delta, that._view._blockSize - lastBlock.items.length);
utilities.insertAdjacentHTMLUnsafe(lastBlock.element, "beforeend", WinJS.UI._repeat("", toAdd));
oldSize = lastBlock.items.length;
children = lastBlock.element.children;
for (var j = 0; j < toAdd; j++) {
lastBlock.items.push(children[oldSize + j]);
}
delta -= toAdd;
}
var blocksCount = Math.floor(delta / that._view._blockSize),
lastBlockSize = delta % that._view._blockSize;
var blockMarkup = "
";
blocksCount++;
}
var blocksTemp = document.createElement("div");
utilities.setInnerHTMLUnsafe(blocksTemp, markup);
var children = blocksTemp.children;
for (var j = 0; j < blocksCount; j++) {
var block = children[j],
blockNode = {
element: block,
items: WinJS.UI._nodeListToArray(block.children)
};
itemsContainer.itemsBlocks.push(blockNode);
}
} else if (delta < 0) {
for (var n = delta; n < 0; n++) {
var container = lastBlock.items.pop();
if (!that._view._requireFocusRestore && container.contains(document.activeElement)) {
that._view._requireFocusRestore = document.activeElement;
that._unsetFocusOnItem();
}
lastBlock.element.removeChild(container);
removedContainers.push(container);
if (!lastBlock.items.length) {
if (itemsContainer.element === lastBlock.element.parentNode) {
itemsContainer.element.removeChild(lastBlock.element);
}
blocks.pop();
lastBlock = blocks[blocks.length - 1];
}
}
}
for (var j = 0, len = blocks.length; j < len; j++) {
var block = blocks[j];
for (var n = 0; n < block.items.length; n++) {
newContainers.push(block.items[n]);
}
}
}
function addInserted(groupNode, firstItemIndex, newSize) {
var added = modifiedElements.filter(function (entry) {
return (entry.oldIndex === -1 && entry.newIndex >= firstItemIndex && entry.newIndex < (firstItemIndex + newSize));
}).sort(function (left, right) {
return left.newIndex - right.newIndex;
});
var itemsContainer = groupNode.itemsContainer;
for (var i = 0, len = added.length; i < len; i++) {
var entry = added[i],
offset = entry.newIndex - firstItemIndex;
var container = createContainer(),
next = offset < itemsContainer.items.length ? itemsContainer.items[offset] : null;
itemsContainer.items.splice(offset, 0, container);
itemsContainer.element.insertBefore(container, next);
}
}
function updateExistingGroup(groupNode, firstItem, newSize) {
if (firstItem + newSize > maxContainers) {
newSize = maxContainers - firstItem;
}
var itemsContainer = groupNode.itemsContainer,
delta = newSize - itemsContainer.items.length;
if (delta > 0) {
var children = itemsContainer.element.children,
oldSize = children.length;
utilities.insertAdjacentHTMLUnsafe(itemsContainer.element, "beforeend", WinJS.UI._repeat("", delta));
for (var n = 0; n < delta; n++) {
var container = children[oldSize + n];
itemsContainer.items.push(container);
}
}
for (var n = delta; n < 0; n++) {
var container = itemsContainer.items.pop();
itemsContainer.element.removeChild(container);
removedContainers.push(container);
}
for (var n = 0, len = itemsContainer.items.length; n < len; n++) {
newContainers.push(itemsContainer.items[n]);
}
}
function addNewGroup(groupInfo, firstItem) {
var header = that._view._createHeaderContainer(prevElement);
var groupNode = {
header: header,
itemsContainer: {
element: that._view._createItemsContainer(header),
}
};
groupNode.itemsContainer[that._view._blockSize ? "itemsBlocks" : "items"] = [];
if (that._view._blockSize) {
updateExistingGroupWithBlocks(groupNode, firstItem, groupInfo.size);
} else {
updateExistingGroup(groupNode, firstItem, groupInfo.size);
}
return groupNode;
}
function shift(groupNode, oldFirstItemIndex, currentFirstItemIndex, newSize) {
var currentLast = currentFirstItemIndex + newSize - 1,
firstShifted,
delta;
for (var i = 0, len = modifiedElements.length; i < len; i++) {
var entry = modifiedElements[i];
if (entry.newIndex >= currentFirstItemIndex && entry.newIndex <= currentLast && entry.oldIndex !== -1) {
if (firstShifted !== +firstShifted || entry.newIndex < firstShifted) {
firstShifted = entry.newIndex;
delta = entry.newIndex - entry.oldIndex;
}
}
}
if (firstShifted === +firstShifted) {
var addedBeforeShift = 0;
for (i = 0, len = modifiedElements.length; i < len; i++) {
var entry = modifiedElements[i];
if (entry.newIndex >= currentFirstItemIndex && entry.newIndex < firstShifted && entry.oldIndex === -1) {
addedBeforeShift++;
}
}
var removedBeforeShift = 0,
oldFirstShifted = firstShifted - delta;
for (i = 0, len = modifiedElements.length; i < len; i++) {
var entry = modifiedElements[i];
if (entry.oldIndex >= oldFirstItemIndex && entry.oldIndex < oldFirstShifted && entry.newIndex === -1) {
removedBeforeShift++;
}
}
delta += removedBeforeShift;
delta -= addedBeforeShift;
delta -= currentFirstItemIndex - oldFirstItemIndex;
var itemsContainer = groupNode.itemsContainer;
if (delta > 0) {
var children = itemsContainer.element.children,
oldSize = children.length;
utilities.insertAdjacentHTMLUnsafe(itemsContainer.element, "afterBegin", WinJS.UI._repeat("", delta));
for (var n = 0; n < delta; n++) {
var container = children[n];
itemsContainer.items.splice(n, 0, container);
}
}
for (var n = delta; n < 0; n++) {
var container = itemsContainer.items.shift();
itemsContainer.element.removeChild(container);
}
if (delta) {
// Invalidate the layout of the entire group because we do not know the exact indices which were added/modified since they were before the realization range.
that._affectedRange.add({
start: currentFirstItemIndex,
end: currentFirstItemIndex + newSize
}, count);
}
}
}
function flatIndexToGroupIndex(index) {
var firstItem = 0;
for (var i = 0, len = that._view.tree.length; i < len; i++) {
var group = that._view.tree[i],
size = group.itemsContainer.items.length,
lastItem = firstItem + size - 1;
if (index >= firstItem && index <= lastItem) {
return {
group: i,
item: index - firstItem
};
}
firstItem += size;
}
}
var oldFirstItem = [];
var firstItem = 0;
if (!that._view._blockSize) {
for (var i = 0, len = this._view.tree.length; i < len; i++) {
oldFirstItem.push(firstItem);
firstItem += this._view.tree[i].itemsContainer.items.length;
}
}
if (!that._view._blockSize) {
var removed = modifiedElements.filter(function (entry) {
return entry.newIndex === -1 && !entry._removalHandled;
}).sort(function (left, right) {
return right.oldIndex - left.oldIndex;
});
for (var i = 0, len = removed.length; i < len; i++) {
var entry = removed[i];
entry._removalHandled = true;
var itemBox = entry._itemBox;
entry._itemBox = null;
var groupIndex = flatIndexToGroupIndex(entry.oldIndex);
var group = this._view.tree[groupIndex.group];
var container = group.itemsContainer.items[groupIndex.item];
container.parentNode.removeChild(container);
if (utilities.hasClass(itemBox, WinJS.UI._selectedClass)) {
utilities.addClass(container, WinJS.UI._selectedClass);
}
group.itemsContainer.items.splice(groupIndex.item, 1);
entry.element = container;
}
}
this._view._modifiedGroups = [];
var prevElement = this._canvasProxy;
firstItem = 0;
// When groups are disabled, loop thru all of the groups (there's only 1).
// When groups are enabled, loop until either we exhaust all of the groups in the data source
// or we exhaust all of the containers that have been created so far.
for (var i = 0, len = groups.length; i < len && (!this._groupsEnabled() || firstItem < maxContainers) ; i++) {
var groupInfo = groups[i],
existingGroupIndex = this._view.keyToGroupIndex[groupInfo.key],
existingGroup = this._view.tree[existingGroupIndex];
if (existingGroup) {
if (that._view._blockSize) {
updateExistingGroupWithBlocks(existingGroup, firstItem, groupInfo.size);
} else {
shift(existingGroup, oldFirstItem[existingGroupIndex], firstItem, groupInfo.size);
addInserted(existingGroup, firstItem, groupInfo.size);
updateExistingGroup(existingGroup, firstItem, groupInfo.size);
}
newTree.push(existingGroup);
newKeyToGroupIndex[groupInfo.key] = newTree.length - 1;
delete this._view.keyToGroupIndex[groupInfo.key];
prevElement = existingGroup.itemsContainer.element;
this._view._modifiedGroups.push({
oldIndex: existingGroupIndex,
newIndex: newTree.length - 1,
element: existingGroup.header
});
} else {
var newGroup = addNewGroup(groupInfo, firstItem);
newTree.push(newGroup);
newKeyToGroupIndex[groupInfo.key] = newTree.length - 1;
this._view._modifiedGroups.push({
oldIndex: -1,
newIndex: newTree.length - 1,
element: newGroup.header
});
prevElement = newGroup.itemsContainer.element;
}
firstItem += groupInfo.size;
}
var removedBlocks = [],
removedItemsContainers = [],
removedHeaders = [],
removedGroups = this._view.keyToGroupIndex ? Object.keys(this._view.keyToGroupIndex) : [];
for (var i = 0, len = removedGroups.length; i < len; i++) {
var groupIndex = this._view.keyToGroupIndex[removedGroups[i]],
groupNode = this._view.tree[groupIndex];
removedHeaders.push(groupNode.header);
removedItemsContainers.push(groupNode.itemsContainer.element);
if (this._view._blockSize) {
for (var b = 0; b < groupNode.itemsContainer.itemsBlocks.length; b++) {
var block = groupNode.itemsContainer.itemsBlocks[b];
for (var n = 0; n < block.items.length; n++) {
removedContainers.push(block.items[n]);
}
}
} else {
for (var n = 0; n < groupNode.itemsContainer.items.length; n++) {
removedContainers.push(groupNode.itemsContainer.items[n]);
}
}
this._view._modifiedGroups.push({
oldIndex: groupIndex,
newIndex: -1,
element: groupNode.header
});
}
for (var i = 0, len = modifiedElements.length; i < len; i++) {
if (modifiedElements[i].newIndex === -1 && !modifiedElements[i]._removalHandled) {
modifiedElements[i]._removalHandled = true;
var itemBox = modifiedElements[i]._itemBox;
modifiedElements[i]._itemBox = null;
var container;
if (removedContainers.length) {
container = removedContainers.pop();
utilities.empty(container);
} else {
container = createContainer();
}
if (utilities.hasClass(itemBox, WinJS.UI._selectedClass)) {
utilities.addClass(container, WinJS.UI._selectedClass);
}
container.appendChild(itemBox);
modifiedElements[i].element = container;
}
}
this._view.tree = newTree;
this._view.keyToGroupIndex = newKeyToGroupIndex;
this._view.containers = newContainers;
return {
removedHeaders: removedHeaders,
removedItemsContainers: removedItemsContainers
};
},
_writeProfilerMark: function ListView_writeProfilerMark(text) {
var message = "WinJS.UI.ListView:" + this._id + ":" + text;
msWriteProfilerMark(message);
WinJS.log && WinJS.log(message, null, "listviewprofiler");
}
}, {
// Static members
triggerDispose: function () {
///
///
/// Triggers the ListView disposal service manually. In normal operation this is triggered
/// at ListView instantiation. However in some scenarios it may be appropriate to run
/// the disposal service manually.
///
///
WinJS.UI._disposeControls();
}
});
WinJS.Class.mix(ListView, WinJS.Utilities.createEventProperties(
"iteminvoked",
"groupheaderinvoked",
"selectionchanging",
"selectionchanged",
"loadingstatechanged",
"keyboardnavigating",
"contentanimating",
"itemdragstart",
"itemdragenter",
"itemdragend",
"itemdragbetween",
"itemdragleave",
"itemdragchanged",
"itemdragdrop",
"accessibilityannotationcomplete"));
WinJS.Class.mix(ListView, WinJS.UI.DOMEventMixin);
return ListView;
}),
_isSelectionRendered: function ListView_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(WinJS.UI._selectionPartsSelector).length > 0;
}
});
})(this, WinJS);
(function repeaterInit(WinJS) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Uses templates to generate HTML from a set of data.
///
///
///
/// ]]>
/// The Repeater control itself
///
///
///
Repeater: WinJS.Namespace._lazy(function () {
var UI = WinJS.UI;
var Utilities = WinJS.Utilities;
// Constants
var ITEMSLOADED = "itemsloaded",
ITEMCHANGING = "itemchanging",
ITEMCHANGED = "itemchanged",
ITEMINSERTING = "iteminserting",
ITEMINSERTED = "iteminserted",
ITEMMOVING = "itemmoving",
ITEMMOVED = "itemmoved",
ITEMREMOVING = "itemremoving",
ITEMREMOVED = "itemremoved",
ITEMSRELOADING = "itemsreloading",
ITEMSRELOADED = "itemsreloaded";
var createEvent = Utilities._createEventProperty;
// Class Names
var repeaterClass = "win-repeater";
function stringifyItem(dataItem) {
// Repeater uses this as its default renderer when no template is provided.
var itemElement = document.createElement("div");
itemElement.innerText = JSON.stringify(dataItem);
return itemElement;
}
// Statics
var strings = {
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; },
get asynchronousRender() { return WinJS.Resources._getWinJSString("ui/asynchronousRender").value; },
get repeaterReentrancy() { return WinJS.Resources._getWinJSString("ui/repeaterReentrancy").value; },
};
var Repeater = WinJS.Class.define(function Repeater_ctor(element, options) {
///
///
/// Creates a new Repeater control.
///
///
/// The DOM element that will host the new control. The Repeater will create an element if this value is null.
///
///
/// An object that contains one or more property/value pairs to apply to the
/// new Repeater. Each property of the options object corresponds to one of the
/// object's properties or events. Event names must begin with "on".
///
///
/// The new Repeater control.
///
///
// Check to make sure we weren't duplicated
if (element && element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.Repeater.DuplicateConstruction", strings.duplicateConstruction);
}
this._element = element || document.createElement("div");
this._id = this._element.id || this._element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
options = options || {};
Utilities.addClass(this._element, "win-repeater win-disposable");
this._render = null;
this._modifying = false;
this._disposed = false;
this._element.winControl = this;
this._dataListeners = {
itemchanged: this._dataItemChangedHandler.bind(this),
iteminserted: this._dataItemInsertedHandler.bind(this),
itemmoved: this._dataItemMovedHandler.bind(this),
itemremoved: this._dataItemRemovedHandler.bind(this),
reload: this._dataReloadHandler.bind(this),
};
// Consume Repeater innerHTML and return a template.
var inlineTemplate = this._extractInlineTemplate();
this._initializing = true;
// Use the inlinetemplate if a parameter was not given.
// Either way, Repeater's innerHTML has now been consumed.
this.template = options.template || inlineTemplate;
this.data = options.data;
this._initializing = false;
UI._setOptions(this, options, true); // Events only
this._repeatedDOM = [];
this._renderAllItems();
this.dispatchEvent(ITEMSLOADED, {});
this._writeProfilerMark("constructor,StopTM");
}, {
///
/// Gets the DOM element that hosts the Repeater.
///
element: {
get: function () {
return this._element;
}
},
///
/// Gets or sets the WinJS.Binding.List that provides the Repeater control with items to display.
///
data: {
get: function () { return this._data; },
set: function (data) {
this._writeProfilerMark("data.set,StartTM");
if (this._data) {
this._removeDataListeners();
};
this._data = data || new WinJS.Binding.List();
this._addDataListeners();
if (!this._initializing) {
this._reloadRepeater(true);
this.dispatchEvent(ITEMSLOADED, {});
}
this._writeProfilerMark("data.set,StopTM");
}
},
///
/// Gets or sets a Template or custom rendering function that defines the HTML of each item within the Repeater.
///
template: {
get: function () { return this._template; },
set: function (template) {
this._writeProfilerMark("template.set,StartTM");
this._template = (template || stringifyItem);
this._render = WinJS.Utilities._syncRenderer(this._template, this.element.tagName);
if (!this._initializing) {
this._reloadRepeater(true);
this.dispatchEvent(ITEMSLOADED, {});
}
this._writeProfilerMark("template.set,StopTM");
}
},
///
/// Gets the number of items in the Repeater control.
///
length: {
get: function () { return this._repeatedDOM.length; },
},
elementFromIndex: function Repeater_elementFromIndex(index) {
///
///
/// Returns the HTML element for the item with the specified index.
///
///
/// The index of the item.
///
///
/// The DOM element for the specified item.
///
///
return this._repeatedDOM[index];
},
dispose: function Repeater_dispose() {
///
///
/// Prepare this Repeater for garbage collection.
///
///
if (this._disposed) {
return;
}
this._disposed = true; // Mark this control as disposed.
this._removeDataListeners();
this._data = null;
this._template = null;
for (var i = 0, len = this._repeatedDOM.length; i < len; i++) {
WinJS.Utilities._disposeElement(this._repeatedDOM[i]);
}
},
///
/// Raised when the Repeater has finished loading a new set of data. This event is only fired on construction
/// or when the Repeater control's data source or template is replaced.
///
onitemsloaded: createEvent(ITEMSLOADED),
///
/// Raised after an item in the Repeater control's data source changes but before the corresponding DOM element has been updated.
///
onitemchanging: createEvent(ITEMCHANGING),
///
/// Raised after an item in the Repeater control's data source changes and after the corresponding DOM element has been updated.
///
onitemchanged: createEvent(ITEMCHANGED),
///
/// Raised after an item has been added to the Repeater control's data source but before the corresponding DOM element has been added.
///
oniteminserting: createEvent(ITEMINSERTING),
///
/// Raised after an item has been added to the Repeater control's data source and after the corresponding DOM element has been added.
///
oniteminserted: createEvent(ITEMINSERTED),
///
/// Raised after an item has been moved from one index to another in the Repeater control's data source but before the corresponding DOM element has been moved.
///
onitemmoving: createEvent(ITEMMOVING),
///
/// Raised after an item has been moved from one index to another in the Repeater control's data source and after the corresponding DOM element has been moved.
///
onitemmoved: createEvent(ITEMMOVED),
///
/// Raised after an item has been removed from the Repeater control's data source but before the corresponding DOM element has been removed.
///
onitemremoving: createEvent(ITEMREMOVING),
///
/// Raised after an item has been removed from one index to another in the Repeater control's data source and after the corresponding DOM element has been removed.
///
onitemremoved: createEvent(ITEMREMOVED),
///
/// The list has been refreshed and any references to data in the list may be incorrect.
/// Raised after the Repeater control's underlying data has been updated but before the updated HTML has been reloaded.
///
onitemsreloading: createEvent(ITEMSRELOADING),
///
/// Raised after the Repeater control's underlying data has been updated and after the updated HTML has been reloaded.
///
onitemsreloaded: createEvent(ITEMSRELOADED),
_extractInlineTemplate: function Repeater_extractInlineTemplate() {
// Creates and returns a WinJS.BindingTemplate from the Repeater innerHTML.
if (this._element.firstElementChild) {
var templateElement = document.createElement(this._element.tagName);
while (this._element.firstElementChild) {
// Move each child element from the Repeater to the Template Element
templateElement.appendChild(this._element.firstElementChild);
}
return new WinJS.Binding.Template(templateElement, { extractChild: true });
}
},
_renderAllItems: function Repeater_renderAllItems() {
var fragment = document.createDocumentFragment();
for (var i = 0, len = this._data.length; i < len; i++) {
var renderedItem = this._render(this._data.getAt(i));
if (!renderedItem) {
throw new WinJS.ErrorFromName("WinJS.UI.Repeater.AsynchronousRender", strings.asynchronousRender);
}
fragment.appendChild(renderedItem);
this._repeatedDOM.push(renderedItem);
}
this._element.appendChild(fragment);
},
_reloadRepeater: function Repeater_reloadRepeater(shouldDisposeElements) {
this._unloadRepeatedDOM(shouldDisposeElements);
this._repeatedDOM = [];
this._renderAllItems();
},
_unloadRepeatedDOM: function Repeater_unloadRepeatedDOM(shouldDisposeElements) {
for (var i = 0, len = this._repeatedDOM.length; i < len; i++) {
if (!!shouldDisposeElements) {
// this_dataReloadHandler uses this to defer disposal until after animations have completed,
// at which point it manually disposes each element.
WinJS.Utilities._disposeElement(this._repeatedDOM[i]);
}
this._element.removeChild(this._repeatedDOM[i]);
}
},
_addDataListeners: function Repeater_addDataListeners() {
Object.keys(this._dataListeners).forEach(function (eventName) {
this._data.addEventListener(eventName, this._dataListeners[eventName], false);
}.bind(this));
},
_beginModification: function Repeater_beginModification() {
if (this._modifying) {
throw new WinJS.ErrorFromName("WinJS.UI.Repeater.RepeaterModificationReentrancy", strings.repeaterReentrancy);
}
this._modifying = true;
},
_endModification: function Repeater_endModification() {
this._modifying = false;
},
_removeDataListeners: function Repeater_removeDataListeners() {
Object.keys(this._dataListeners).forEach(function (eventName) {
this._data.removeEventListener(eventName, this._dataListeners[eventName], false);
}.bind(this));
},
_dataItemChangedHandler: function Repeater_dataItemChangedHandler(eventInfo) {
// Handles the 'itemchanged' event fired by WinJS.Binding.List
this._beginModification();
var animationPromise;
var root = this._element;
var index = eventInfo.detail.index;
var renderedItem = this._render(eventInfo.detail.newValue);
if (!renderedItem) {
throw new WinJS.ErrorFromName("WinJS.UI.Repeater.AsynchronousRender", strings.asynchronousRender);
}
// Append to the event object
if (this._repeatedDOM[index]) {
eventInfo.detail.oldElement = this._repeatedDOM[index];
}
eventInfo.detail.newElement = renderedItem;
eventInfo.detail.setPromise = function setPromise(delayPromise) {
animationPromise = delayPromise;
};
this._writeProfilerMark(ITEMCHANGING + ",info");
this.dispatchEvent(ITEMCHANGING, eventInfo.detail);
// Make the change
var oldItem = null;
if (index < this._repeatedDOM.length) {
oldItem = this._repeatedDOM[index];
root.replaceChild(renderedItem, oldItem);
this._repeatedDOM[index] = renderedItem;
} else {
root.appendChild(renderedItem);
this._repeatedDOM.push(renderedItem);
}
this._endModification();
this._writeProfilerMark(ITEMCHANGED + ",info");
this.dispatchEvent(ITEMCHANGED, eventInfo.detail);
if (oldItem) { // Give the option to delay element disposal.
WinJS.Promise.as(animationPromise).done(function () {
WinJS.Utilities._disposeElement(oldItem);
}.bind(this));
}
},
_dataItemInsertedHandler: function Repeater_dataItemInsertedHandler(eventInfo) {
// Handles the 'iteminserted' event fired by WinJS.Binding.List
this._beginModification();
var index = eventInfo.detail.index;
var renderedItem = this._render(eventInfo.detail.value);
if (!renderedItem) {
throw new WinJS.ErrorFromName("WinJS.UI.Repeater.AsynchronousRender", strings.asynchronousRender);
}
var root = this._element;
eventInfo.detail.affectedElement = renderedItem;
this._writeProfilerMark(ITEMINSERTING + ",info");
this.dispatchEvent(ITEMINSERTING, eventInfo.detail);
if (index < this._repeatedDOM.length) {
var nextSibling = this._repeatedDOM[index];
root.insertBefore(renderedItem, nextSibling);
} else {
root.appendChild(renderedItem);
}
// Update collection of rendered elements
this._repeatedDOM.splice(index, 0, renderedItem);
this._endModification();
this._writeProfilerMark(ITEMINSERTED + ",info");
this.dispatchEvent(ITEMINSERTED, eventInfo.detail);
},
_dataItemMovedHandler: function Repeater_dataItemMovedHandler(eventInfo) {
// Handles the 'itemmoved' event fired by WinJS.Binding.List
this._beginModification();
var movingItem = this._repeatedDOM[eventInfo.detail.oldIndex];
// Fire the event before we start the move.
eventInfo.detail.affectedElement = movingItem;
this._writeProfilerMark(ITEMMOVING + ",info");
this.dispatchEvent(ITEMMOVING, eventInfo.detail);
// Remove
this._repeatedDOM.splice(eventInfo.detail.oldIndex, 1)[0];
movingItem.parentNode.removeChild(movingItem);
// Insert
if (eventInfo.detail.newIndex < (this._data.length) - 1) {
var nextSibling = this._repeatedDOM[eventInfo.detail.newIndex];
this._element.insertBefore(movingItem, nextSibling);
this._repeatedDOM.splice(eventInfo.detail.newIndex, 0, movingItem);
} else {
this._repeatedDOM.push(movingItem);
this._element.appendChild(movingItem);
}
this._endModification();
this._writeProfilerMark(ITEMMOVED + ",info");
this.dispatchEvent(ITEMMOVED, eventInfo.detail);
},
_dataItemRemovedHandler: function Repeater_dataItemRemoveHandler(eventInfo) {
// Handles the 'itemremoved' event fired by WinJS.Binding.List
this._beginModification();
var animationPromise;
var oldItem = this._repeatedDOM[eventInfo.detail.index];
// Trim 'value' and 'key' from the eventInfo.details that Binding.List gave for the removal case,
// since both of those properties already exist inside of eventInfo.details.item.
var eventDetail = { affectedElement: oldItem, index: eventInfo.detail.index, item: eventInfo.detail.item };
eventDetail.setPromise = function setPromise(delayPromise) {
animationPromise = delayPromise;
}
this._writeProfilerMark(ITEMREMOVING + ",info");
this.dispatchEvent(ITEMREMOVING, eventDetail);
oldItem.parentNode.removeChild(oldItem);
this._repeatedDOM.splice(eventInfo.detail.index, 1);
this._endModification();
this._writeProfilerMark(ITEMREMOVED + ",info");
this.dispatchEvent(ITEMREMOVED, eventDetail);
WinJS.Promise.as(animationPromise).done(function () {
WinJS.Utilities._disposeElement(oldItem);
}.bind(this));
},
_dataReloadHandler: function Repeater_dataReloadHandler(eventInfo) {
// Handles the 'reload' event fired by WinJS.Binding.List whenever it performs operations such as reverse() or sort()
this._beginModification();
var animationPromise;
var shallowCopyBefore = this._repeatedDOM.slice(0);
var eventDetail = { affectedElements: shallowCopyBefore };
eventDetail.setPromise = function (delayPromise) {
animationPromise = delayPromise;
}
this._writeProfilerMark(ITEMSRELOADING + ",info");
this.dispatchEvent(ITEMSRELOADING, eventDetail);
this._reloadRepeater(false /*shouldDisposeElements */);
var shallowCopyAfter = this._repeatedDOM.slice(0);
this._endModification();
this._writeProfilerMark(ITEMSRELOADED + ",info");
this.dispatchEvent(ITEMSRELOADED, { affectedElements: shallowCopyAfter });
WinJS.Promise.as(animationPromise).done(function () { // Gives the option to defer disposal.
for (var i = 0, len = shallowCopyBefore.length; i < len; i++) {
WinJS.Utilities._disposeElement(shallowCopyBefore[i]);
}
}.bind(this));
},
_writeProfilerMark: function Repeater_writeProfilerMark(text) {
msWriteProfilerMark("WinJS.UI.Repeater:" + this._id + ":" + text);
}
}, {
isDeclarativeControlContainer: true,
});
WinJS.Class.mix(Repeater, UI.DOMEventMixin);
return Repeater;
})
});
})(WinJS);
(function selectionManagerInit(global, WinJS, undefined) {
"use strict";
var utilities = WinJS.Utilities,
Promise = WinJS.Promise;
WinJS.Namespace.define("WinJS.UI", {
_ItemSet: WinJS.Namespace._lazy(function () {
var _ItemSet = WinJS.Class.define(function _ItemSet_ctor(listView, ranges, count) {
this._listView = listView;
this._ranges = ranges;
this._itemsCount = count;
});
_ItemSet.prototype = {
getRanges: function () {
var ranges = [];
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
ranges.push({
firstIndex: range.firstIndex,
lastIndex: range.lastIndex,
firstKey: range.firstKey,
lastKey: range.lastKey
});
}
return ranges;
},
getItems: function () {
return WinJS.UI.getItemsFromRanges(this._listView._itemsManager.dataSource, this._ranges);
},
isEverything: function () {
return this.count() === this._itemsCount;
},
count: function () {
var count = 0;
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
count += range.lastIndex - range.firstIndex + 1;
}
return count;
},
getIndices: function () {
var indices = [];
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
for (var n = range.firstIndex; n <= range.lastIndex; n++) {
indices.push(n);
}
}
return indices;
}
};
return _ItemSet;
}),
getItemsFromRanges: function (dataSource, ranges) {
var listBinding = dataSource.createListBinding(),
promises = [];
function getIndices() {
var indices = [];
for (var i = 0, len = ranges.length; i < len; i++) {
var range = ranges[i];
for (var j = range.firstIndex; j <= range.lastIndex; j++) {
indices.push(j);
}
}
return Promise.wrap(indices);
}
return getIndices().then(function (indices) {
for (var i = 0; i < indices.length; i++) {
promises.push(listBinding.fromIndex(indices[i]));
}
return WinJS.Promise.join(promises).then(function (items) {
listBinding.release();
return items;
});
});
},
_Selection: WinJS.Namespace._lazy(function () {
function isEverythingRange(ranges) {
return ranges && ranges.firstIndex === 0 && ranges.lastIndex === Number.MAX_VALUE;
}
return WinJS.Class.derive(WinJS.UI._ItemSet, function (listView, indexesAndRanges) {
this._listView = listView;
this._itemsCount = -1;
this._ranges = [];
if (indexesAndRanges) {
this.set(indexesAndRanges);
}
}, {
clear: function () {
///
///
/// Clears the selection.
///
///
/// A Promise that is fulfilled when the clear operation completes.
///
///
this._releaseRanges(this._ranges);
this._ranges = [];
return Promise.wrap();
},
set: function (items) {
///
///
/// Clears the current selection and replaces it with the specified items.
///
///
/// The indexes or keys of the items that make up the selection.
/// You can provide different types of objects for the items parameter:
/// you can specify an index, a key, or a range of indexes.
/// It can also be an array that contains one or more of these objects.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
// A range with lastIndex set to Number.MAX_VALUE used to mean selectAll. Passing such range to set was equivalent to selectAll. This code preserves this behavior.
if (!isEverythingRange(items)) {
this._releaseRanges(this._ranges);
this._ranges = [];
var that = this;
return this._execute("_set", items).then(function () {
that._ranges.sort(function (left, right) {
return left.firstIndex - right.firstIndex;
});
return that._ensureKeys();
}).then(function () {
return that._ensureCount();
});
} else {
return this.selectAll();
}
},
add: function (items) {
///
///
/// Adds one or more items to the selection.
///
///
/// The indexes or keys of the items to add.
/// You can provide different types of objects for the items parameter:
/// you can specify an index, a key, or a range of indexes.
/// It can also be an array that contains one or more of these objects.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
if (!isEverythingRange(items)) {
var that = this;
return this._execute("_add", items).then(function () {
return that._ensureKeys();
}).then(function () {
return that._ensureCount();
});
} else {
return this.selectAll();
}
},
remove: function (items) {
///
///
/// Removes the specified items from the selection.
///
///
/// The indexes or keys of the items to remove. You can provide different types of objects for the items parameter:
/// you can specify an index, a key, or a range of indexes.
/// It can also be an array that contains one or more of these objects.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
var that = this;
return this._execute("_remove", items).then(function () {
return that._ensureKeys();
});
},
selectAll: function () {
///
///
/// Adds all the items in the ListView to the selection.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
var that = this;
return that._ensureCount().then(function () {
if (that._itemsCount) {
var range = {
firstIndex: 0,
lastIndex: that._itemsCount - 1,
};
that._retainRange(range);
that._releaseRanges(that._ranges);
that._ranges = [range];
return that._ensureKeys();
}
});
},
/*#DBG
_assertValid: function () {
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
_ASSERT(range.firstIndex <= range.lastIndex);
_ASSERT(!i || this._ranges[i - 1].lastIndex < range.firstIndex);
}
return true;
},
#DBG*/
_execute: function (operation, items) {
var that = this,
keysSupported = !!that._getListBinding().fromKey,
array = Array.isArray(items) ? items : [items],
promises = [Promise.wrap()];
function toRange(type, first, last) {
var retVal = {};
retVal["first" + type] = first;
retVal["last" + type] = last;
return retVal;
}
function handleKeys(range) {
var binding = that._getListBinding();
var promise = Promise.join([binding.fromKey(range.firstKey), binding.fromKey(range.lastKey)]).then(function (items) {
if (items[0] && items[1]) {
range.firstIndex = items[0].index;
range.lastIndex = items[1].index;
that[operation](range);
}
return range;
});
promises.push(promise);
}
for (var i = 0, len = array.length; i < len; i++) {
var item = array[i];
if (typeof item === "number") {
this[operation](toRange("Index", item, item));
} else if (item) {
if (keysSupported && item.key !== undefined) {
handleKeys(toRange("Key", item.key, item.key));
} else if (keysSupported && item.firstKey !== undefined && item.lastKey !== undefined) {
handleKeys(toRange("Key", item.firstKey, item.lastKey));
} else if (item.index !== undefined && typeof item.index === "number") {
this[operation](toRange("Index", item.index, item.index));
} else if (item.firstIndex !== undefined && item.lastIndex !== undefined &&
typeof item.firstIndex === "number" && typeof item.lastIndex === "number") {
this[operation](toRange("Index", item.firstIndex, item.lastIndex));
}
}
}
return Promise.join(promises);
},
_set: function (range) {
this._retainRange(range);
this._ranges.push(range);
},
_add: function (newRange) {
var that = this,
prev = null,
range,
inserted;
var merge = function (left, right) {
if (right.lastIndex > left.lastIndex) {
left.lastIndex = right.lastIndex;
left.lastKey = right.lastKey;
if (left.lastPromise) {
left.lastPromise.release();
}
left.lastPromise = that._getListBinding().fromIndex(left.lastIndex).retain();
}
}
for (var i = 0, len = this._ranges.length; i < len; i++) {
range = this._ranges[i];
if (newRange.firstIndex < range.firstIndex) {
var mergeWithPrev = prev && newRange.firstIndex < (prev.lastIndex + 1);
if (mergeWithPrev) {
inserted = i - 1;
merge(prev, newRange);
} else {
this._insertRange(i, newRange);
inserted = i;
}
break;
} else if (newRange.firstIndex === range.firstIndex) {
merge(range, newRange);
inserted = i;
break;
}
prev = range;
}
if (inserted === undefined) {
var last = this._ranges.length ? this._ranges[this._ranges.length - 1] : null,
mergeWithLast = last && newRange.firstIndex < (last.lastIndex + 1);
if (mergeWithLast) {
merge(last, newRange);
} else {
this._retainRange(newRange);
this._ranges.push(newRange);
}
} else {
prev = null;
for (i = inserted + 1, len = this._ranges.length; i < len; i++) {
range = this._ranges[i];
if (newRange.lastIndex < range.firstIndex) {
mergeWithPrev = prev && prev.lastIndex > newRange.lastIndex;
if (mergeWithPrev) {
merge(this._ranges[inserted], prev);
}
this._removeRanges(inserted + 1, i - inserted - 1);
break;
} else if (newRange.lastIndex === range.firstIndex) {
merge(this._ranges[inserted], range);
this._removeRanges(inserted + 1, i - inserted);
break;
}
prev = range;
}
if (i >= len) {
merge(this._ranges[inserted], this._ranges[len - 1]);
this._removeRanges(inserted + 1, len - inserted - 1);
}
}
},
_remove: function (toRemove) {
var that = this;
function retainPromise(index) {
return that._getListBinding().fromIndex(index).retain();
}
// This method is called when a range needs to be unselected. It is inspecting every range in the current selection comparing
// it to the range which is being unselected and it is building an array of new selected ranges
var ranges = [];
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
if (range.lastIndex < toRemove.firstIndex || range.firstIndex > toRemove.lastIndex) {
// No overlap with the unselected range
ranges.push(range);
} else if (range.firstIndex < toRemove.firstIndex && range.lastIndex >= toRemove.firstIndex && range.lastIndex <= toRemove.lastIndex) {
// The end of this range is being unselected
ranges.push({
firstIndex: range.firstIndex,
firstKey: range.firstKey,
firstPromise: range.firstPromise,
lastIndex: toRemove.firstIndex - 1,
lastPromise: retainPromise(toRemove.firstIndex - 1)
});
range.lastPromise.release();
} else if (range.lastIndex > toRemove.lastIndex && range.firstIndex >= toRemove.firstIndex && range.firstIndex <= toRemove.lastIndex) {
// The beginning of this range is being unselected
ranges.push({
firstIndex: toRemove.lastIndex + 1,
firstPromise: retainPromise(toRemove.lastIndex + 1),
lastIndex: range.lastIndex,
lastKey: range.lastKey,
lastPromise: range.lastPromise
});
range.firstPromise.release();
} else if (range.firstIndex < toRemove.firstIndex && range.lastIndex > toRemove.lastIndex) {
// The middle part of this range is being unselected
ranges.push({
firstIndex: range.firstIndex,
firstKey: range.firstKey,
firstPromise: range.firstPromise,
lastIndex: toRemove.firstIndex - 1,
lastPromise: retainPromise(toRemove.firstIndex - 1),
});
ranges.push({
firstIndex: toRemove.lastIndex + 1,
firstPromise: retainPromise(toRemove.lastIndex + 1),
lastIndex: range.lastIndex,
lastKey: range.lastKey,
lastPromise: range.lastPromise
});
} else {
// The whole range is being unselected
//#DBG _ASSERT(range.firstIndex >= toRemove.firstIndex && range.lastIndex <= toRemove.lastIndex);
range.firstPromise.release();
range.lastPromise.release();
}
}
this._ranges = ranges;
},
_ensureKeys: function () {
var promises = [Promise.wrap()];
var that = this;
var ensureKey = function (which, range) {
var keyProperty = which + "Key";
if (!range[keyProperty]) {
var promise = range[which + "Promise"];
promise.then(function (item) {
if (item) {
range[keyProperty] = item.key;
}
});
return promise;
} else {
return Promise.wrap();
}
}
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
promises.push(ensureKey("first", range));
promises.push(ensureKey("last", range));
}
Promise.join(promises).then(function () {
that._ranges = that._ranges.filter(function (range) {
return range.firstKey && range.lastKey;
});
});
return Promise.join(promises);
},
_mergeRanges: function (target, source) {
//#DBG _ASSERT(!target.lastPromise && !source.lastPromise);
target.lastIndex = source.lastIndex;
target.lastKey = source.lastKey;
},
_isIncluded: function (index) {
if (this.isEverything()) {
return true;
} else {
for (var i = 0, len = this._ranges.length; i < len; i++) {
var range = this._ranges[i];
if (range.firstIndex <= index && index <= range.lastIndex) {
return true;
}
}
return false;
}
},
_ensureCount: function () {
var that = this;
return this._listView._itemsCount().then(function (count) {
that._itemsCount = count;
});
},
_insertRange: function (index, newRange) {
this._retainRange(newRange);
this._ranges.splice(index, 0, newRange);
},
_removeRanges: function (index, howMany) {
for (var i = 0; i < howMany; i++) {
this._releaseRange(this._ranges[index + i]);
}
this._ranges.splice(index, howMany);
},
_retainRange: function (range) {
if (!range.firstPromise) {
range.firstPromise = this._getListBinding().fromIndex(range.firstIndex).retain();
}
if (!range.lastPromise) {
range.lastPromise = this._getListBinding().fromIndex(range.lastIndex).retain();
}
},
_retainRanges: function () {
for (var i = 0, len = this._ranges.length; i < len; i++) {
this._retainRange(this._ranges[i]);
}
},
_releaseRange: function (range) {
range.firstPromise.release();
range.lastPromise.release();
},
_releaseRanges: function (ranges) {
for (var i = 0, len = ranges.length; i < len; ++i) {
this._releaseRange(ranges[i]);
}
},
_getListBinding: function () {
return this._listView._itemsManager._listBinding;
}
}, {
supportedForProcessing: false,
});
}),
// This component is responsible for holding selection state
_SelectionManager: WinJS.Namespace._lazy(function () {
var _SelectionManager = function (listView) {
this._listView = listView;
this._selected = new WinJS.UI._Selection(this._listView);
// Don't rename this member. Some apps reference it.
this._pivot = WinJS.UI._INVALID_INDEX;
this._focused = { type: WinJS.UI.ObjectType.item, index: 0 };
this._pendingChange = Promise.wrap();
};
_SelectionManager.prototype = {
count: function () {
///
///
/// Returns the number of items in the selection.
///
///
/// The number of items in the selection.
///
///
return this._selected.count();
},
getIndices: function () {
///
///
/// Returns a list of the indexes for the items in the selection.
///
///
/// The list of indexes for the items in the selection as an array of Number objects.
///
///
return this._selected.getIndices();
},
getItems: function () {
///
///
/// Returns an array that contains the items in the selection.
///
///
/// A Promise that contains an array of the requested IItem objects.
///
///
return this._selected.getItems();
},
getRanges: function () {
///
///
/// Gets an array of the index ranges for the selected items.
///
///
/// An array that contains an ISelectionRange object for each index range in the selection.
///
///
return this._selected.getRanges();
},
isEverything: function () {
///
///
/// Returns a value that indicates whether the selection contains every item in the data source.
///
///
/// true if the selection contains every item in the data source; otherwise, false.
///
///
return this._selected.isEverything();
},
set: function (items) {
///
///
/// Clears the current selection and replaces it with the specified items.
///
///
/// The indexes or keys of the items that make up the selection.
/// You can provide different types of objects for the items parameter:
/// you can specify an index, a key, or a range of indexes.
/// It can also be an array that contains one or more of these objects.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
var that = this,
signal = new WinJS._Signal();
return this._synchronize(signal).then(function () {
var newSelection = new WinJS.UI._Selection(that._listView);
return newSelection.set(items).then(
function () {
that._set(newSelection);
signal.complete();
},
function (error) {
newSelection.clear();
signal.complete();
return WinJS.Promise.wrapError(error);
}
);
});
},
clear: function () {
///
///
/// Clears the selection.
///
///
/// A Promise that is fulfilled when the clear operation completes.
///
///
var that = this,
signal = new WinJS._Signal();
return this._synchronize(signal).then(function () {
var newSelection = new WinJS.UI._Selection(that._listView);
return newSelection.clear().then(
function () {
that._set(newSelection);
signal.complete();
},
function (error) {
newSelection.clear();
signal.complete();
return WinJS.Promise.wrapError(error);
}
);
});
},
add: function (items) {
///
///
/// Adds one or more items to the selection.
///
///
/// The indexes or keys of the items to add.
/// You can provide different types of objects for the items parameter:
/// you can specify an index, a key, or a range of indexes.
/// It can also be an array that contains one or more of these objects.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
var that = this,
signal = new WinJS._Signal();
return this._synchronize(signal).then(function () {
var newSelection = that._cloneSelection();
return newSelection.add(items).then(
function () {
that._set(newSelection);
signal.complete();
},
function (error) {
newSelection.clear();
signal.complete();
return WinJS.Promise.wrapError(error);
}
);
});
},
remove: function (items) {
///
///
/// Removes the specified items from the selection.
///
///
/// The indexes or keys of the items to remove. You can provide different types of objects for the items parameter:
/// you can specify an index, a key, or a range of indexes.
/// It can also be an array that contains one or more of these objects.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
var that = this,
signal = new WinJS._Signal();
return this._synchronize(signal).then(function () {
var newSelection = that._cloneSelection();
return newSelection.remove(items).then(
function () {
that._set(newSelection);
signal.complete();
},
function (error) {
newSelection.clear();
signal.complete();
return WinJS.Promise.wrapError(error);
}
);
});
},
selectAll: function () {
///
///
/// Adds all the items in the ListView to the selection.
///
///
/// A Promise that is fulfilled when the operation completes.
///
///
var that = this,
signal = new WinJS._Signal();
return this._synchronize(signal).then(function () {
var newSelection = new WinJS.UI._Selection(that._listView);
return newSelection.selectAll().then(
function () {
that._set(newSelection);
signal.complete();
},
function (error) {
newSelection.clear();
signal.complete();
return WinJS.Promise.wrapError(error);
}
);
});
},
_synchronize: function (signal) {
var that = this;
return this._listView._versionManager.unlocked.then(function () {
var currentPendingChange = that._pendingChange;
that._pendingChange = WinJS.Promise.join([currentPendingChange, signal.promise]).then(function () { });
return currentPendingChange;
});
},
_reset: function () {
this._pivot = WinJS.UI._INVALID_INDEX;
this._setFocused({ type: WinJS.UI.ObjectType.item, index: 0 }, this._keyboardFocused());
this._pendingChange.cancel();
this._pendingChange = Promise.wrap();
this._selected.clear();
this._selected = new WinJS.UI._Selection(this._listView);
},
_dispose: function () {
this._selected.clear();
this._selected = null;
this._listView = null;
},
_set: function (newSelection) {
var that = this;
return this._fireSelectionChanging(newSelection).then(function (approved) {
if (approved) {
that._selected.clear();
that._selected = newSelection;
that._listView._updateSelection();
that._fireSelectionChanged();
} else {
newSelection.clear();
}
return approved;
});
},
_fireSelectionChanging: function (newSelection) {
var eventObject = document.createEvent("CustomEvent"),
newSelectionUpdated = Promise.wrap();
eventObject.initCustomEvent("selectionchanging", true, true, {
newSelection: newSelection,
preventTapBehavior: function () {
},
setPromise: function (promise) {
///
///
/// Used to inform the ListView that asynchronous work is being performed, and that this
/// event handler should not be considered complete until the promise completes.
///
///
/// The promise to wait for.
///
///
newSelectionUpdated = promise;
}
});
var approved = this._listView._element.dispatchEvent(eventObject);
return newSelectionUpdated.then(function () {
return approved;
});
},
_fireSelectionChanged: function () {
var eventObject = document.createEvent("CustomEvent");
eventObject.initCustomEvent("selectionchanged", true, false, null);
this._listView._element.dispatchEvent(eventObject);
},
_getFocused: function () {
return { type: this._focused.type, index: this._focused.index };
},
_setFocused: function (entity, keyboardFocused) {
this._focused = { type: entity.type, index: entity.index };
this._focusedByKeyboard = keyboardFocused;
},
_keyboardFocused: function () {
return this._focusedByKeyboard;
},
_updateCount: function (count) {
this._selected._itemsCount = count;
},
_isIncluded: function (index) {
return this._selected._isIncluded(index);
},
_cloneSelection: function () {
var newSelection = new WinJS.UI._Selection(this._listView);
newSelection._ranges = this._selected.getRanges();
newSelection._itemsCount = this._selected._itemsCount;
newSelection._retainRanges();
return newSelection;
}
};
_SelectionManager.supportedForProcessing = false;
return _SelectionManager;
})
});
})(this, WinJS);
(function virtualizeContentsViewInit(global, WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
_VirtualizeContentsView: WinJS.Namespace._lazy(function () {
var utilities = WinJS.Utilities,
Promise = WinJS.Promise,
Scheduler = WinJS.Utilities.Scheduler;
function cooperativeQueueWorker(info) {
var workItems = info.job._workItems;
var work;
while (workItems.length && !info.shouldYield) {
work = workItems.shift();
work();
}
info.setWork(cooperativeQueueWorker);
if (!workItems.length) {
info.job.pause();
}
}
function scheduleQueueJob(priority, name) {
var job = Scheduler.schedule(cooperativeQueueWorker, priority, null, name);
job._workItems = [];
job.addWork = function (work, head) {
if (head) {
this._workItems.unshift(work);
} else {
this._workItems.push(work);
}
this.resume();
};
job.clearWork = function () {
this._workItems.length = 0;
};
job.dispose = function () {
this.cancel();
this._workItems.length = 0;
}
return job;
}
function shouldWaitForSeZo(listView) {
return listView._zooming || listView._pinching;
}
function waitForSeZo(listView, timeout) {
// waitForSeZo will block until sezo calls endZoom on listview, or a timeout duration has elapsed to
// unblock a potential deadlock between the sezo waiting on container creation, and container creation
// waiting on endZoom.
if (listView._isZombie()) { return Promise.wrap(); }
if (shouldWaitForSeZo(listView)) {
if (+timeout !== timeout) {
timeout = WinJS.UI._VirtualizeContentsView._waitForSeZoTimeoutDuration;
}
//To improve SeZo's zoom animation and pinch detection perf, we want to ensure unimportant task
//is only run while zooming or pinching is not in progress.
return Promise.timeout(WinJS.UI._VirtualizeContentsView._waitForSeZoIntervalDuration).then(function () {
timeout -= WinJS.UI._VirtualizeContentsView._waitForSeZoIntervalDuration;
if (timeout <= 0) {
return true;
}
return waitForSeZo(listView, timeout);
});
} else {
return Promise.wrap();
}
}
function makeFunctor(scrollToFunctor) {
if (typeof scrollToFunctor === "number") {
var pos = scrollToFunctor;
scrollToFunctor = function () {
return {
position: pos,
direction: "right"
};
};
}
return scrollToFunctor;
}
var _VirtualizeContentsView = function VirtualizeContentsView_ctor(listView) {
this._listView = listView;
this._forceRelayout = false;
this.items = new WinJS.UI._ItemsContainer(listView);
this.firstIndexDisplayed = -1;
this.lastIndexDisplayed = -1;
this.begin = 0;
this.end = 0;
this._realizePass = 1;
this._firstLayoutPass = true;
this._runningAnimations = null;
this._renderCompletePromise = Promise.wrap();
this._state = new CreatedState(this);
this._createLayoutSignal();
this._createTreeBuildingSignal();
this._layoutWork = null;
this._onscreenJob = scheduleQueueJob(Scheduler.Priority.aboveNormal, "on-screen items");
this._frontOffscreenJob = scheduleQueueJob(Scheduler.Priority.normal, "front off-screen items");
this._backOffscreenJob = scheduleQueueJob(Scheduler.Priority.belowNormal, "back off-screen items");
this._scrollbarPos = 0;
this._direction = "right";
this._scrollToFunctor = makeFunctor(0);
};
_VirtualizeContentsView._pagesToPrefetch = 2;
_VirtualizeContentsView._waitForSeZoIntervalDuration = 100;
_VirtualizeContentsView._waitForSeZoTimeoutDuration = 500;
_VirtualizeContentsView._chunkSize = 500;
_VirtualizeContentsView._startupChunkSize = 100;
_VirtualizeContentsView._maxTimePerCreateContainers = 5;
_VirtualizeContentsView._createContainersJobTimeslice = 15;
_VirtualizeContentsView._blocksToRelease = 10;
_VirtualizeContentsView._realizationLevel = {
skip: "skip",
realize: "realize",
normal: "normal"
};
_VirtualizeContentsView.prototype = {
_dispose: function VirtualizeContentsView_dispose() {
this.cleanUp();
this.items = null;
this._renderCompletePromise && this._renderCompletePromise.cancel();
this._renderCompletePromise = null;
this._onscreenJob.dispose();
this._frontOffscreenJob.dispose();
this._backOffscreenJob.dispose();
},
_createItem: function VirtualizeContentsView_createItem(itemIndex, itemPromise, available, unavailable) {
this._listView._writeProfilerMark("createItem(" + itemIndex + ") " + this._getBoundingRectString(itemIndex) + ",info");
var that = this;
that._listView._itemsManager._itemFromItemPromiseThrottled(itemPromise).done(
function (element) {
if (element) {
available(itemIndex, element, that._listView._itemsManager._recordFromElement(element));
} else {
unavailable(itemIndex);
}
},
function (err) {
unavailable(itemIndex);
return WinJS.Promise.wrapError(err);
}
);
},
_addItem: function VirtualizeContentsView_addItem(fragment, itemIndex, element, currentPass) {
/*#DBG
if (!WinJS.Utilities.data(element).itemsManagerRecord || WinJS.Utilities.data(element).removeElementMapRecord) {
throw "ACK! Attempt to add an item to the scrollview which hasn't yet been realized";
}
#DBG*/
if (this._realizePass === currentPass) {
var record = this._listView._itemsManager._recordFromElement(element);
delete this._pendingItemPromises[record.itemPromise.handle];
this.items.setItemAt(itemIndex, {
itemBox: null,
container: null,
element: element,
detached: true,
itemsManagerRecord: record
});
}
},
finalItem: function VirtualizeContentsView_finalItem() {
return this.containers ? Promise.wrap(this.containers.length - 1) : Promise.cancel;
},
_setSkipRealizationForChange: function (skip) {
if (skip) {
if (this._realizationLevel !== WinJS.UI._VirtualizeContentsView._realizationLevel.realize) {
this._realizationLevel = WinJS.UI._VirtualizeContentsView._realizationLevel.skip;
}
} else {
this._realizationLevel = WinJS.UI._VirtualizeContentsView._realizationLevel.realize;
}
},
_realizeItems: function VirtualizeContentsView_realizeItems(fragment, begin, end, count, currentPass, scrollbarPos, direction, firstInView, lastInView, ignoreGaps) {
var perfId = "_realizeItems(" + begin + "-" + (end - 1) + ") visible(" + firstInView + "-" + lastInView + ")";
this._listView._writeProfilerMark(perfId + ",StartTM");
direction = direction || "right";
var counter = end - begin;
var inView = lastInView - firstInView + 1,
inViewCounter = inView,
rightOffscreenCount = end - lastInView - 1,
leftOffscreenCount = firstInView - begin;
var renderCompletePromises = [];
var entranceAnimationSignal = new WinJS._Signal();
var viewportItemsRealized = new WinJS._Signal();
var frontItemsRealized = new WinJS._Signal();
var that = this;
function itemIsReady(itemIndex, itemsManagerRecord) {
renderCompletePromises.push(WinJS.Promise._cancelBlocker(itemsManagerRecord.renderComplete));
delivered(itemIndex);
}
function appendItemsToDom(startIndex, endIndex) {
that._listView._writeProfilerMark("_realizeItems_appendedItemsToDom,StartTM");
if (that._listView._isZombie()) { return; }
function updateSwipeable(itemData, element, itemBox) {
if (!itemData.updatedSwipeableAttribute && (that._listView.itemsDraggable || that._listView.itemsReorderable || that._listView._swipeable)) {
itemData.itemsManagerRecord.renderComplete.done(function () {
if (that._realizePass === currentPass) {
var dragDisabledOnItem = utilities.hasClass(element, WinJS.UI._nonDraggableClass),
selectionDisabledOnItem = utilities.hasClass(element, WinJS.UI._nonSelectableClass),
dragEnabled = (that._listView.itemsDraggable || that._listView.itemsReorderable),
swipeSelectEnabled = (that._listView._selectionAllowed() && that._listView._swipeBehavior === WinJS.UI.SwipeBehavior.select);
if (dragEnabled && !dragDisabledOnItem) {
itemData.itemBox.draggable = true;
}
if (that._listView._swipeable && ((dragEnabled && !swipeSelectEnabled && dragDisabledOnItem) ||
(swipeSelectEnabled && !dragEnabled && selectionDisabledOnItem) ||
(dragDisabledOnItem && selectionDisabledOnItem))) {
utilities.addClass(itemData.itemBox, WinJS.UI._nonSwipeableClass);
}
itemData.updatedSwipeableAttribute = true;
}
});
}
}
var itemIndex;
var appendItemsCount = 0;
var firstIndex = -1;
var lastIndex = -1;
for (itemIndex = startIndex; itemIndex <= endIndex; itemIndex++) {
var itemData = that.items.itemDataAt(itemIndex);
if (itemData) {
var element = itemData.element,
itemBox = itemData.itemBox;
if (!itemBox) {
itemBox = that._listView._itemBoxTemplate.cloneNode(true);
itemData.itemBox = itemBox;
itemBox.appendChild(element);
utilities.addClass(element, WinJS.UI._itemClass);
that._listView._setupAriaSelectionObserver(element);
if (that._listView._isSelected(itemIndex)) {
WinJS.UI._ItemEventsHandler.renderSelection(itemBox, element, true, true);
}
that._listView._currentMode().renderDragSourceOnRealizedItem(itemIndex, itemBox);
}
updateSwipeable(itemData, element, itemBox);
var container = that.getContainer(itemIndex);
if (itemBox.parentNode !== container) {
that._appendAndRestoreFocus(container, itemBox);
appendItemsCount++;
if (firstIndex < 0) {
firstIndex = itemIndex;
}
lastIndex = itemIndex;
itemData.container = container;
if (that._listView._isSelected(itemIndex)) {
utilities.addClass(container, WinJS.UI._selectedClass);
}
utilities.removeClass(container, WinJS.UI._backdropClass);
// elementAvailable needs to be called after fragment.appendChild. elementAvailable fulfills a promise for items requested
// by the keyboard focus handler. That handler will explicitly call .focus() on the element, so in order for
// the focus handler to work, the element needs to be in a tree prior to focusing.
that.items.elementAvailable(itemIndex);
}
}
}
that._listView._writeProfilerMark("_realizeItems_appendedItemsToDom,StopTM");
if (appendItemsCount > 0) {
that._listView._writeProfilerMark("_realizeItems_appendedItemsToDom:" + appendItemsCount + " (" + firstIndex + "-" + lastIndex + "),info");
that._reportElementsLevel(direction);
}
}
function removeGaps(first, last, begin, end) {
if (ignoreGaps) {
return;
}
// If we realized items 0 through 20 and then scrolled so that items 25 - 30 are on screen when we
// append them to the dom we should remove items 0 - 20 from the dom so there are no gaps between the
// two realized spots.
// Walk backwards from the beginning and if we find an item which is missing remove the rest
var foundMissing = false;
while (first >= begin) {
foundMissing = testGap(first, foundMissing);
first--;
}
// Walk forwards from the end and if we find an item which is missing remove the rest
foundMissing = false;
while (last <= end) {
foundMissing = testGap(last, foundMissing);
last++;
}
function testGap(itemIndex, foundMissing) {
// This helper method is called for each index and once an item is missing from the dom
// it removes any future one it encounters.
var itemData = that.items.itemDataAt(itemIndex);
if (itemData) {
var itemBox = itemData.itemBox;
if (!itemBox || !itemBox.parentNode) {
return true;
} else if (foundMissing) {
utilities.addClass(itemBox.parentNode, WinJS.UI._backdropClass);
itemBox.parentNode.removeChild(itemBox);
return true;
} else {
return false;
}
} else {
return true;
}
}
}
function scheduleReadySignal(first, last, job, dir, head) {
var promises = [];
for (var i = first; i <= last; i++) {
var itemData = that.items.itemDataAt(i);
if (itemData) {
promises.push(itemData.itemsManagerRecord.itemPromise);
}
}
function schedule(itemIndex) {
var itemData = that.items.itemDataAt(itemIndex);
if (itemData) {
var record = itemData.itemsManagerRecord;
if (!record.readyComplete && that._realizePass === currentPass) {
job.addWork(function () {
if (that._listView._isZombie()) {
return;
}
if (record.pendingReady && that._realizePass === currentPass) {
that._listView._writeProfilerMark("pendingReady(" + itemIndex + "),info");
record.pendingReady();
}
}, head);
}
}
}
Promise.join(promises).then(function () {
if (dir === "right") {
for (var i = first; i <= last; i++) {
schedule(i);
}
} else {
for (var i = last; i >= first; i--) {
schedule(i);
}
}
});
}
function delivered(index) {
if (that._realizePass !== currentPass) {
return;
}
if (index >= firstInView && index <= lastInView) {
if (--inViewCounter === 0) {
appendItemsToDom(firstInView, lastInView);
removeGaps(firstInView, lastInView, begin, end);
if (that._firstLayoutPass) {
scheduleReadySignal(firstInView, lastInView, that._frontOffscreenJob, direction === "right" ? "left" : "right", true);
var entranceAnimation = Scheduler.schedulePromiseHigh(null, "WinJS.UI.ListView.entranceAnimation").then(function () {
if (that._listView._isZombie()) { return; }
that._listView._writeProfilerMark("entranceAnimation,StartTM");
var promise = that._listView._animateListEntrance(!that._firstEntranceAnimated);
that._firstEntranceAnimated = true;
return promise;
});
that._runningAnimations = Promise.join([that._runningAnimations, entranceAnimation]);
that._runningAnimations.done(function () {
that._listView._writeProfilerMark("entranceAnimation,StopTM");
if (that._realizePass === currentPass) {
that._runningAnimations = null;
entranceAnimationSignal.complete();
}
});
that._firstLayoutPass = false;
if (that._listView._isCurrentZoomView) {
Scheduler.requestDrain(that._onscreenJob.priority);
}
} else {
// during scrolling ready for onscreen items after front off screen items
scheduleReadySignal(firstInView, lastInView, that._frontOffscreenJob, direction);
entranceAnimationSignal.complete();
}
that._updateHeaders(that._listView._canvas, firstInView, lastInView + 1).done(function () {
viewportItemsRealized.complete();
});
}
} else if (index < firstInView) {
--leftOffscreenCount;
if (leftOffscreenCount % inView === 0) {
appendItemsToDom(begin, firstInView - 1);
}
if (!leftOffscreenCount) {
that._updateHeaders(that._listView._canvas, begin, firstInView).done(function () {
if (direction !== "right") {
frontItemsRealized.complete();
}
});
scheduleReadySignal(begin, firstInView - 1, direction !== "right" ? that._frontOffscreenJob : that._backOffscreenJob, "left");
}
} else if (index > lastInView) {
--rightOffscreenCount;
if (rightOffscreenCount % inView === 0) {
appendItemsToDom(lastInView + 1, end - 1);
}
if (!rightOffscreenCount) {
that._updateHeaders(that._listView._canvas, lastInView + 1, end).then(function () {
if (direction === "right") {
frontItemsRealized.complete();
}
});
scheduleReadySignal(lastInView + 1, end - 1, direction === "right" ? that._frontOffscreenJob : that._backOffscreenJob, "right");
}
}
counter--;
if (counter === 0) {
that._renderCompletePromise = Promise.join(renderCompletePromises).then(null, function (e) {
var error = Array.isArray(e) && e.some(function (item) { return item && !(item instanceof Error && item.name === "Canceled"); });
if (error) {
// rethrow
return Promise.wrapError(e);
}
});
(that._headerRenderPromises || Promise.wrap()).done(function () {
Scheduler.schedule(function () {
if (that._listView._isZombie()) {
workCompleteSignal.cancel();
} else {
workCompleteSignal.complete();
}
}, Math.min(that._onscreenJob.priority, that._backOffscreenJob.priority), null, "WinJS.UI.ListView._allItemsRealized");
});
}
}
function newItemIsReady(itemIndex, element, itemsManagerRecord) {
if (that._realizePass === currentPass) {
//#DBG _ASSERT(!itemsManagerRecord.released);
var element = itemsManagerRecord.element;
that._addItem(fragment, itemIndex, element, currentPass);
itemIsReady(itemIndex, itemsManagerRecord);
}
}
if (counter > 0) {
var createCount = 0;
var updateCount = 0;
var cleanCount = 0;
//#DBG _ASSERT(lastInView < end);
that.firstIndexDisplayed = firstInView;
that.lastIndexDisplayed = lastInView;
var isCurrentZoomView = that._listView._isCurrentZoomView;
if (that._highPriorityRealize && (that._firstLayoutPass || that._hasAnimationInViewportPending)) {
// startup or edits that will animate items in the viewport
that._highPriorityRealize = false;
that._onscreenJob.priority = Scheduler.Priority.high;
that._frontOffscreenJob.priority = Scheduler.Priority.normal;
that._backOffscreenJob.priority = Scheduler.Priority.belowNormal;
} else if (that._highPriorityRealize) {
// edits that won't animate items in the viewport
that._highPriorityRealize = false;
that._onscreenJob.priority = Scheduler.Priority.high;
that._frontOffscreenJob.priority = Scheduler.Priority.high - 1;
that._backOffscreenJob.priority = Scheduler.Priority.high - 1;
} else if (isCurrentZoomView) {
// scrolling
that._onscreenJob.priority = Scheduler.Priority.aboveNormal;
that._frontOffscreenJob.priority = Scheduler.Priority.normal;
that._backOffscreenJob.priority = Scheduler.Priority.belowNormal;
} else {
// hidden ListView in SeZo
that._onscreenJob.priority = Scheduler.Priority.belowNormal;
that._frontOffscreenJob.priority = Scheduler.Priority.idle;
that._backOffscreenJob.priority = Scheduler.Priority.idle;
}
// Create a promise to wrap the work in the queue. When the queue gets to the last item we can mark
// the work promise complete and if the work promise is canceled we cancel the queue.
//
var workCompleteSignal = new WinJS._Signal();
// If the version manager recieves a notification we clear the work in the work queues
//
var cancelToken = that._listView._versionManager.cancelOnNotification(workCompleteSignal.promise);
var queueStage1AfterStage0 = function (job, record) {
if (record.startStage1) {
record.stage0.then(function () {
if (that._realizePass === currentPass && record.startStage1) {
job.addWork(record.startStage1);
}
});
}
};
var queueWork = function (job, itemIndex) {
var itemData = that.items.itemDataAt(itemIndex);
if (!itemData) {
var itemPromise = that._listView._itemsManager._itemPromiseAtIndex(itemIndex);
// Remember this pending item promise and avoid canceling it from the previous realization pass.
that._pendingItemPromises[itemPromise.handle] = itemPromise;
delete that._previousRealizationPendingItemPromises[itemPromise.handle];
job.addWork(function VirtualizeContentsView_realizeItemsWork() {
if (that._listView._isZombie()) {
return;
}
createCount++;
that._createItem(itemIndex, itemPromise, newItemIsReady, delivered);
// _createItem runs user code
if (that._listView._isZombie() || that._realizePass !== currentPass) {
return;
}
if (itemPromise.handle) {
var record = that._listView._itemsManager._recordFromHandle(itemPromise.handle);
queueStage1AfterStage0(job, record);
}
});
}
};
var queueRight = function (job, first, last) {
for (var itemIndex = first; itemIndex <= last; itemIndex++) {
queueWork(job, itemIndex);
}
};
var queueLeft = function (job, first, last) {
// Always build the left side in the direction away from the center.
for (var itemIndex = last; itemIndex >= first; itemIndex--) {
queueWork(job, itemIndex);
}
};
var handleExistingRange = function (job, first, last) {
for (var itemIndex = first; itemIndex <= last; itemIndex++) {
var itemData = that.items.itemDataAt(itemIndex);
if (itemData) {
var record = itemData.itemsManagerRecord;
itemIsReady(itemIndex, record);
updateCount++;
queueStage1AfterStage0(job, record);
}
}
};
// PendingItemPromises are the item promises which we have requested from the ItemsManager
// which have not returned an element (placeholder or real). Since we only clean up items
// which have an element in _unrealizeItems we need to remember these item promises. We cancel
// the item promises from the previous realization iteration if those item promises are not
// used for the current realization.
this._previousRealizationPendingItemPromises = this._pendingItemPromises || {};
this._pendingItemPromises = {};
var emptyFront;
if (direction === "left") {
queueLeft(that._onscreenJob, firstInView, lastInView);
queueLeft(that._frontOffscreenJob, begin, firstInView - 1);
emptyFront = begin > (firstInView - 1);
} else {
queueRight(that._onscreenJob, firstInView, lastInView);
queueRight(that._frontOffscreenJob, lastInView + 1, end - 1);
emptyFront = lastInView + 1 > (end - 1);
}
// Anything left in _previousRealizationPendingItemPromises can be canceled here.
// Note: we are doing this synchronously. If we didn't do it synchronously we would have had to merge
// _previousRealizationPendingItemPromises and _pendingItemPromises together. This also has the great
// benefit to cancel item promises in the backOffScreenArea which are much less important.
for (var i = 0, handles = Object.keys(this._previousRealizationPendingItemPromises), len = handles.length; i < len; i++) {
var handle = handles[i];
that._listView._itemsManager.releaseItemPromise(this._previousRealizationPendingItemPromises[handle]);
}
this._previousRealizationPendingItemPromises = {};
// Handle existing items in the second pass to make sure that raising ready signal is added to the queues after creating items
handleExistingRange(that._onscreenJob, firstInView, lastInView);
if (direction === "left") {
handleExistingRange(that._frontOffscreenJob, begin, firstInView - 1);
} else {
handleExistingRange(that._frontOffscreenJob, lastInView + 1, end - 1);
}
var showProgress = (inViewCounter === lastInView - firstInView + 1);
if (that._firstLayoutPass) {
that._listView._canvas.style.opacity = 0;
} else {
if (showProgress) {
that._listView._showProgressBar(that._listView._element, "50%", "50%");
} else {
that._listView._hideProgressBar();
}
}
that._frontOffscreenJob.pause();
that._backOffscreenJob.pause();
viewportItemsRealized.promise.done(
function () {
that._frontOffscreenJob.resume();
if (emptyFront) {
frontItemsRealized.complete();
}
},
function () {
workCompleteSignal.cancel();
}
);
frontItemsRealized.promise.done(function () {
that._listView._writeProfilerMark("frontItemsRealized,info");
if (direction === "left") {
queueRight(that._backOffscreenJob, lastInView + 1, end - 1);
handleExistingRange(that._backOffscreenJob, lastInView + 1, end - 1);
} else {
queueLeft(that._backOffscreenJob, begin, firstInView - 1);
handleExistingRange(that._backOffscreenJob, begin, firstInView - 1);
}
that._backOffscreenJob.resume();
});
workCompleteSignal.promise.done(
function () {
that._listView._versionManager.clearCancelOnNotification(cancelToken);
that._listView._writeProfilerMark(perfId + " complete(created:" + createCount + " updated:" + updateCount + "),info");
},
function (err) {
that._listView._versionManager.clearCancelOnNotification(cancelToken);
that._onscreenJob.clearWork();
that._frontOffscreenJob.clearWork();
that._backOffscreenJob.clearWork();
entranceAnimationSignal.cancel();
viewportItemsRealized.cancel();
that._listView._writeProfilerMark(perfId + " canceled(created:" + createCount + " updated:" + updateCount + " clean:" + cleanCount + "),info");
return WinJS.Promise.wrapError(err);
}
);
that._listView._writeProfilerMark(perfId + ",StopTM");
return {
viewportItemsRealized: viewportItemsRealized.promise,
allItemsRealized: workCompleteSignal.promise,
loadingCompleted: Promise.join([workCompleteSignal.promise, entranceAnimationSignal.promise]).then(function () {
var promises = [];
for (var i = begin; i < end; i++) {
var itemData = that.items.itemDataAt(i);
if (itemData) {
promises.push(itemData.itemsManagerRecord.itemReadyPromise);
}
}
return WinJS.Promise._cancelBlocker(Promise.join(promises));
})
};
}
else {
that._listView._writeProfilerMark(perfId + ",StopTM");
return {
viewportItemsRealized: Promise.wrap(),
allItemsRealized: Promise.wrap(),
loadingCompleted: Promise.wrap()
};
}
},
_setAnimationInViewportState: function VirtualizeContentsView_setAnimationInViewportState(modifiedElements) {
this._hasAnimationInViewportPending = false;
if (modifiedElements && modifiedElements.length > 0) {
var viewportLength = this._listView._getViewportLength(),
range = this._listView._layout.itemsFromRange(this._scrollbarPos, this._scrollbarPos + viewportLength - 1);
for (var i = 0, len = modifiedElements.length; i < len; i++) {
var modifiedElement = modifiedElements[i];
if (modifiedElement.newIndex >= range.firstIndex && modifiedElement.newIndex <= range.lastIndex && modifiedElement.newIndex !== modifiedElement.oldIndex) {
this._hasAnimationInViewportPending = true;
break;
}
}
}
},
_addHeader: function VirtualizeContentsView_addHeader(fragment, groupIndex) {
var that = this;
return this._listView._groups.renderGroup(groupIndex).then(function (header) {
if (header) {
var placeholder = that._getHeaderContainer(groupIndex);
if (header.element.parentNode !== placeholder) {
placeholder.appendChild(header.element);
utilities.addClass(header.element, WinJS.UI._headerClass);
}
that._listView._groups.setDomElement(groupIndex, header.element);
}
});
},
_updateHeaders: function VirtualizeContentsView_updateHeaders(fragment, begin, end) {
var that = this;
function updateGroup(index) {
var group = that._listView._groups.group(index);
if (group && !group.header) {
var headerPromise = group.headerPromise;
if (!headerPromise) {
headerPromise = group.headerPromise = that._addHeader(fragment, index);
headerPromise.done(function () {
group.headerPromise = null;
}, function () {
group.headerPromise = null;
});
}
return headerPromise;
}
return Promise.wrap();
}
this._listView._groups.removeElements();
var groupStart = this._listView._groups.groupFromItem(begin),
groupIndex = groupStart,
groupEnd = this._listView._groups.groupFromItem(end - 1),
realizationPromises = [];
if (groupIndex !== null) {
//#DBG _ASSERT(groupEnd !== null);
for (; groupIndex <= groupEnd; groupIndex++) {
realizationPromises.push(updateGroup(groupIndex));
}
}
function done() {
that._headerRenderPromises = null;
}
this._headerRenderPromises = Promise.join(realizationPromises, this._headerRenderPromises).then(done, done);
return this._headerRenderPromises || Promise.wrap();
},
_unrealizeItem: function VirtualizeContentsView_unrealizeItem(itemIndex) {
var listView = this._listView,
focusedItemPurged;
this._listView._writeProfilerMark("_unrealizeItem(" + itemIndex + "),info");
var focused = listView._selection._getFocused();
if (focused.type !== WinJS.UI.ObjectType.groupHeader && focused.index === itemIndex) {
listView._unsetFocusOnItem();
focusedItemPurged = true;
}
var itemData = this.items.itemDataAt(itemIndex),
item = itemData.element,
itemBox = itemData.itemBox;
if (itemBox && itemBox.parentNode) {
utilities.removeClass(itemBox.parentNode, WinJS.UI._selectedClass);
utilities.removeClass(itemBox.parentNode, WinJS.UI._footprintClass);
utilities.addClass(itemBox.parentNode, WinJS.UI._backdropClass);
itemBox.parentNode.removeChild(itemBox);
}
itemData.container = null;
if (listView._currentMode().itemUnrealized) {
listView._currentMode().itemUnrealized(itemIndex, itemBox);
}
this.items.removeItem(itemIndex);
// If this wasn't released by the itemsManager already, then
// we remove it. This handles the special case of delete
// occuring on an item that is outside of the current view, but
// has not been cleaned up yet.
//
if (!itemData.removed) {
listView._itemsManager.releaseItem(item);
}
WinJS.Utilities._disposeElement(item);
if (focusedItemPurged) {
// If the focused item was purged, we'll still want to focus on it if it comes into view sometime in the future.
// calling _setFocusOnItem once the item is removed from this.items will set up a promise that will be fulfilled
// if the item ever gets reloaded
listView._setFocusOnItem(listView._selection._getFocused());
}
},
_unrealizeGroup: function VirtualizeContentsView_unrealizeGroup(group) {
var headerElement = group.header,
focusedItemPurged;
var focused = this._listView._selection._getFocused();
if (focused.type === WinJS.UI.ObjectType.groupHeader && this._listView._groups.group(focused.index) === group) {
this._listView._unsetFocusOnItem();
focusedItemPurged = true;
}
if (headerElement.parentNode) {
headerElement.parentNode.removeChild(headerElement);
}
WinJS.Utilities._disposeElement(headerElement);
group.header = null;
group.left = -1;
group.top = -1;
if (focusedItemPurged) {
this._listView._setFocusOnItem(this._listView._selection._getFocused());
}
},
_unrealizeItems: function VirtualizeContentsView_unrealizeItems(remove) {
var that = this,
removedCount = 0;
this.items.eachIndex(function (index) {
if (index < that.begin || index >= that.end) {
that._unrealizeItem(index);
return remove && ++removedCount >= remove;
}
});
var groups = this._listView._groups,
beginGroup = groups.groupFromItem(this.begin);
if (beginGroup !== null) {
var endGroup = groups.groupFromItem(this.end - 1);
for (var i = 0, len = groups.length() ; i < len; i++) {
var group = groups.group(i);
if ((i < beginGroup || i > endGroup) && group.header) {
this._unrealizeGroup(group);
}
}
}
},
_unrealizeExcessiveItems: function VirtualizeContentsView_unrealizeExcessiveItems() {
var realized = this.items.count(),
needed = this.end - this.begin,
approved = needed + this._listView._maxDeferredItemCleanup;
this._listView._writeProfilerMark("_unrealizeExcessiveItems realized(" + realized + ") approved(" + approved + "),info");
if (realized > approved) {
this._unrealizeItems(realized - approved);
}
},
_lazilyUnrealizeItems: function VirtualizeContentsView_lazilyUnrealizeItems() {
this._listView._writeProfilerMark("_lazilyUnrealizeItems,StartTM");
var that = this;
return waitForSeZo(this._listView).then(function () {
function done() {
that._listView._writeProfilerMark("_lazilyUnrealizeItems,StopTM");
}
if (that._listView._isZombie()) {
done();
return;
}
var itemsToUnrealize = [];
that.items.eachIndex(function (index) {
if (index < that.begin || index >= that.end) {
itemsToUnrealize.push(index);
}
});
that._listView._writeProfilerMark("_lazilyUnrealizeItems itemsToUnrealize(" + itemsToUnrealize.length + "),info");
var groupsToUnrealize = [],
groups = that._listView._groups,
beginGroup = groups.groupFromItem(that.begin);
if (beginGroup !== null) {
var endGroup = groups.groupFromItem(that.end - 1);
for (var i = 0, len = groups.length() ; i < len; i++) {
var group = groups.group(i);
if ((i < beginGroup || i > endGroup) && group.header) {
groupsToUnrealize.push(group);
}
}
}
if (itemsToUnrealize.length || groupsToUnrealize.length) {
var job;
var promise = new Promise(function (complete) {
function unrealizeWorker(info) {
if (that._listView._isZombie()) { return; }
var firstIndex = -1,
lastIndex = -1,
removeCount = 0,
zooming = shouldWaitForSeZo(that._listView);
while (itemsToUnrealize.length && !zooming && !info.shouldYield) {
var itemIndex = itemsToUnrealize.shift();
that._unrealizeItem(itemIndex);
removeCount++;
if (firstIndex < 0) {
firstIndex = itemIndex;
}
lastIndex = itemIndex;
}
that._listView._writeProfilerMark("unrealizeWorker removeItems:" + removeCount + " (" + firstIndex + "-" + lastIndex + "),info");
while (groupsToUnrealize.length && !zooming && !info.shouldYield) {
that._unrealizeGroup(groupsToUnrealize.shift());
}
if (itemsToUnrealize.length || groupsToUnrealize.length) {
if (zooming) {
info.setPromise(waitForSeZo(that._listView).then(function () {
return unrealizeWorker;
}));
} else {
info.setWork(unrealizeWorker);
}
} else {
complete();
}
}
job = Scheduler.schedule(unrealizeWorker, Scheduler.Priority.belowNormal, null, "WinJS.UI.ListView._lazilyUnrealizeItems");
});
return promise.then(done, function (error) {
job.cancel();
that._listView._writeProfilerMark("_lazilyUnrealizeItems canceled,info");
that._listView._writeProfilerMark("_lazilyUnrealizeItems,StopTM");
return Promise.wrapError(error);
});
} else {
done();
return Promise.wrap();
}
});
},
_getBoundingRectString: function VirtualizeContentsView_getBoundingRectString(itemIndex) {
var result;
if (itemIndex >= 0 && itemIndex < this.containers.length) {
var itemPos = this._listView._layout._getItemPosition(itemIndex);
if (itemPos) {
result = "[" + itemPos.left + "; " + itemPos.top + "; " + itemPos.width + "; " + itemPos.height + " ]";
}
}
return result || "";
},
_clearDeferTimeout: function VirtualizeContentsView_clearDeferTimeout() {
if (this.deferTimeout) {
this.deferTimeout.cancel();
this.deferTimeout = null;
}
if (this.deferredActionCancelToken !== -1) {
this._listView._versionManager.clearCancelOnNotification(this.deferredActionCancelToken);
this.deferredActionCancelToken = -1;
}
},
_setupAria: function VirtualizeContentsView_setupAria(timedOut) {
if (this._listView._isZombie()) { return; }
var that = this;
function done() {
that._listView._writeProfilerMark("aria work,StopTM");
}
function calcLastRealizedIndexInGroup(groupIndex) {
var groups = that._listView._groups,
nextGroup = groups.group(groupIndex + 1);
return (nextGroup ? Math.min(nextGroup.startIndex - 1, that.end - 1) : that.end - 1);
}
this._listView._createAriaMarkers();
return this._listView._itemsCount().then(function (count) {
if (count > 0 && that.firstIndexDisplayed !== -1 && that.lastIndexDisplayed !== -1) {
that._listView._writeProfilerMark("aria work,StartTM");
var startMarker = that._listView._ariaStartMarker,
endMarker = that._listView._ariaEndMarker,
index = that.begin,
item = that.items.itemAt(that.begin),
job,
// These are only used when the ListView is using groups
groups,
startGroup,
currentGroup,
group,
lastRealizedIndexInGroup;
if (item) {
WinJS.UI._ensureId(item);
if (that._listView._groupsEnabled()) {
groups = that._listView._groups;
startGroup = currentGroup = groups.groupFromItem(that.begin);
group = groups.group(currentGroup);
lastRealizedIndexInGroup = calcLastRealizedIndexInGroup(currentGroup);
WinJS.UI._ensureId(group.header);
WinJS.UI._setAttribute(group.header, "role", that._listView._headerRole);
WinJS.UI._setAttribute(group.header, "x-ms-aria-flowfrom", startMarker.id);
WinJS.UI._setFlow(group.header, item);
WinJS.UI._setAttribute(group.header, "tabindex", that._listView._tabIndex);
} else {
WinJS.UI._setAttribute(item, "x-ms-aria-flowfrom", startMarker.id);
}
return new Promise(function (completeJobPromise) {
var skipWait = timedOut;
job = Scheduler.schedule(function ariaWorker(jobInfo) {
if (that._listView._isZombie()) {
done();
return;
}
for (; index < that.end; index++) {
if (!skipWait && shouldWaitForSeZo(that._listView)) {
jobInfo.setPromise(waitForSeZo(that._listView).then(function (timedOut) {
skipWait = timedOut;
return ariaWorker;
}));
return;
} else if (jobInfo.shouldYield) {
jobInfo.setWork(ariaWorker);
return;
}
item = that.items.itemAt(index);
var nextItem = that.items.itemAt(index + 1);
if (nextItem) {
WinJS.UI._ensureId(nextItem);
}
WinJS.UI._setAttribute(item, "role", that._listView._itemRole);
WinJS.UI._setAttribute(item, "aria-setsize", count);
WinJS.UI._setAttribute(item, "aria-posinset", index + 1);
WinJS.UI._setAttribute(item, "tabindex", that._listView._tabIndex);
if (that._listView._groupsEnabled()) {
if (index === lastRealizedIndexInGroup || !nextItem) {
var nextGroup = groups.group(currentGroup + 1);
// If group is the last realized group, then nextGroup won't exist in the DOM.
// When this is the case, nextItem shouldn't exist.
//#DBG _ASSERT(nextGroup && nextGroup.header || !nextItem);
if (nextGroup && nextGroup.header && nextItem) {
WinJS.UI._setAttribute(nextGroup.header, "tabindex", that._listView._tabIndex);
WinJS.UI._setAttribute(nextGroup.header, "role", that._listView._headerRole);
WinJS.UI._ensureId(nextGroup.header);
WinJS.UI._setFlow(item, nextGroup.header);
WinJS.UI._setFlow(nextGroup.header, nextItem);
} else {
// We're at the last group so flow to the end marker
WinJS.UI._setAttribute(item, "aria-flowto", endMarker.id);
}
currentGroup++;
group = nextGroup;
lastRealizedIndexInGroup = calcLastRealizedIndexInGroup(currentGroup);
} else {
// This is not the last item in the group so flow to the next item
//#DBG _ASSERT(nextItem);
WinJS.UI._setFlow(item, nextItem);
}
} else if (nextItem) {
// Groups are disabled so as long as we aren't at the last item, flow to the next one
WinJS.UI._setFlow(item, nextItem);
} else {
// Groups are disabled and we're at the last item, so flow to the end marker
WinJS.UI._setAttribute(item, "aria-flowto", endMarker.id);
}
if (!nextItem) {
break;
}
}
that._listView._fireAccessibilityAnnotationCompleteEvent(that.begin, index, startGroup, currentGroup - 1);
done();
completeJobPromise();
}, Scheduler.Priority.belowNormal, null, "WinJS.UI.ListView._setupAria");
}, function () {
// Cancellation handler for promise returned by setupAria
job.cancel();
done();
});
} else {
// the first item is null
done();
}
} else {
// The count is 0
return Promise.wrap();
}
});
},
_setupDeferredActions: function VirtualizeContentsView_setupDeferredActions() {
this._listView._writeProfilerMark("_setupDeferredActions,StartTM");
var that = this;
this._clearDeferTimeout();
function cleanUp() {
if (that._listView._isZombie()) { return; }
that.deferTimeout = null;
that._listView._versionManager.clearCancelOnNotification(that.deferredActionCancelToken);
that.deferredActionCancelToken = -1;
}
this.deferTimeout = this._lazilyRemoveRedundantItemsBlocks().then(function() {
return WinJS.Promise.timeout(WinJS.UI._DEFERRED_ACTION);
}).
then(function () {
return waitForSeZo(that._listView);
}).
then(function (timedOut) {
return that._setupAria(timedOut);
}).
then(cleanUp, function (error) {
cleanUp();
return Promise.wrapError(error);
});
this.deferredActionCancelToken = this._listView._versionManager.cancelOnNotification(this.deferTimeout);
this._listView._writeProfilerMark("_setupDeferredActions,StopTM");
},
// Sets aria-flowto on _ariaStartMarker and x-ms-aria-flowfrom on _ariaEndMarker. The former
// points to either the first visible group header or the first visible item. The latter points
// to the last visible item.
_updateAriaMarkers: function VirtualizeContentsView_updateAriaMarkers(listViewIsEmpty, firstIndexDisplayed, lastIndexDisplayed) {
var that = this;
if (this._listView._isZombie()) {
return;
}
function getFirstVisibleItem() {
return that.items.itemAt(firstIndexDisplayed);
}
// At a certain index, the VDS may return null for all items at that index and
// higher. When this is the case, the end marker should point to the last
// non-null item in the visible range.
function getLastVisibleItem() {
for (var i = lastIndexDisplayed; i >= firstIndexDisplayed; i--) {
if (that.items.itemAt(i)) {
return that.items.itemAt(i);
}
}
return null;
}
this._listView._createAriaMarkers();
var startMarker = this._listView._ariaStartMarker,
endMarker = this._listView._ariaEndMarker,
firstVisibleItem,
lastVisibleItem;
if (firstIndexDisplayed !== -1 && lastIndexDisplayed !== -1 && firstIndexDisplayed <= lastIndexDisplayed) {
firstVisibleItem = getFirstVisibleItem();
lastVisibleItem = getLastVisibleItem();
}
if (listViewIsEmpty || !firstVisibleItem || !lastVisibleItem) {
WinJS.UI._setFlow(startMarker, endMarker);
this._listView._fireAccessibilityAnnotationCompleteEvent(-1, -1);
} else {
WinJS.UI._ensureId(firstVisibleItem);
WinJS.UI._ensureId(lastVisibleItem);
// Set startMarker's flowto
if (this._listView._groupsEnabled()) {
var groups = this._listView._groups,
firstVisibleGroup = groups.group(groups.groupFromItem(firstIndexDisplayed));
if (firstVisibleGroup.header) {
WinJS.UI._ensureId(firstVisibleGroup.header);
if (firstIndexDisplayed === firstVisibleGroup.startIndex) {
WinJS.UI._setAttribute(startMarker, "aria-flowto", firstVisibleGroup.header.id);
} else {
WinJS.UI._setAttribute(startMarker, "aria-flowto", firstVisibleItem.id);
}
}
} else {
WinJS.UI._setAttribute(startMarker, "aria-flowto", firstVisibleItem.id);
}
// Set endMarker's flowfrom
WinJS.UI._setAttribute(endMarker, "x-ms-aria-flowfrom", lastVisibleItem.id);
}
},
// Update the ARIA attributes on item that are needed so that Narrator can announce it.
// item must be in the items container.
updateAriaForAnnouncement: function VirtualizeContentsView_updateAriaForAnnouncement(item, count) {
var index = -1;
var type = WinJS.UI.ObjectType.item;
if (WinJS.Utilities.hasClass(item, WinJS.UI._headerClass)) {
index = this._listView._groups.index(item);
//#DBG _ASSERT(index !== WinJS.UI._INVALID_INDEX);
type = WinJS.UI.ObjectType.groupHeader;
WinJS.UI._setAttribute(item, "role", this._listView._headerRole);
} else {
index = this.items.index(item);
//#DBG _ASSERT(index !== WinJS.UI._INVALID_INDEX);
WinJS.UI._setAttribute(item, "aria-setsize", count);
WinJS.UI._setAttribute(item, "aria-posinset", index + 1);
WinJS.UI._setAttribute(item, "role", this._listView._itemRole);
}
if (type === WinJS.UI.ObjectType.groupHeader) {
this._listView._fireAccessibilityAnnotationCompleteEvent(-1, -1, index, index);
} else {
this._listView._fireAccessibilityAnnotationCompleteEvent(index, index, -1, -1);
}
},
_reportElementsLevel: function VirtualizeContentsView_reportElementsLevel(direction) {
var items = this.items;
function elementsCount(first, last) {
var count = 0;
for (var itemIndex = first; itemIndex <= last; itemIndex++) {
var itemData = items.itemDataAt(itemIndex);
if (itemData && itemData.container) {
count++;
}
}
return count;
}
var level;
if (direction === "right") {
level = Math.floor(100 * elementsCount(this.firstIndexDisplayed, this.end - 1) / (this.end - this.firstIndexDisplayed));
} else {
level = Math.floor(100 * elementsCount(this.begin, this.lastIndexDisplayed) / (this.lastIndexDisplayed - this.begin + 1));
}
this._listView._writeProfilerMark("elementsLevel level(" + level + "),info");
},
_createHeaderContainer: function VirtualizeContentsView_createHeaderContainer(insertAfter) {
return this._createSurfaceChild(WinJS.UI._headerContainerClass, insertAfter);
},
_createItemsContainer: function VirtualizeContentsView_createItemsContainer(insertAfter) {
var itemsContainer = this._createSurfaceChild(WinJS.UI._itemsContainerClass, insertAfter);
var padder = document.createElement("div");
padder.className = WinJS.UI._padderClass;
itemsContainer.appendChild(padder);
return itemsContainer;
},
_ensureContainerInDOM: function VirtualizeContentsView_ensureContainerInDOM(index) {
var container = this.containers[index];
if (container && !this._listView._canvas.contains(container)) {
this._forceItemsBlocksInDOM(index, index + 1);
return true;
}
return false;
},
_ensureItemsBlocksInDOM: function VirtualizeContentsView_ensureItemsBlocksInDOM(begin, end) {
if (this._expandedRange) {
var oldBegin = this._expandedRange.first.index,
oldEnd = this._expandedRange.last.index + 1;
if (begin <= oldBegin && end > oldBegin) {
end = Math.max(end, oldEnd);
} else if (begin < oldEnd && end >= oldEnd) {
begin = Math.min(begin, oldBegin);
}
}
this._forceItemsBlocksInDOM(begin, end);
},
_removeRedundantItemsBlocks: function VirtualizeContentsView_removeRedundantItemsBlocks() {
if (this.begin !== -1 && this.end !== -1) {
this._forceItemsBlocksInDOM(this.begin, this.end);
}
},
_lazilyRemoveRedundantItemsBlocks: function VirtualizeContentsView_lazilyRemoveRedundantItemsBlocks() {
this._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks,StartTM");
var that = this;
return waitForSeZo(this._listView).then(function () {
function done() {
that._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks,StopTM");
}
if (that._listView._isZombie()) {
done();
return;
}
if (that._expandedRange && that.begin !== -1 && that.end !== -1 && (that._expandedRange.first.index < that.begin || that._expandedRange.last.index + 1 > that.end)) {
var job;
var promise = new Promise(function (complete) {
function blocksCleanupWorker(info) {
if (that._listView._isZombie()) { return; }
var zooming = shouldWaitForSeZo(that._listView);
while (that._expandedRange.first.index < that.begin && !zooming && !info.shouldYield) {
var begin = Math.min(that.begin, that._expandedRange.first.index + that._blockSize * WinJS.UI._VirtualizeContentsView._blocksToRelease);
that._forceItemsBlocksInDOM(begin, that.end);
}
while (that._expandedRange.last.index + 1 > that.end && !zooming && !info.shouldYield) {
var end = Math.max(that.end, that._expandedRange.last.index - that._blockSize * WinJS.UI._VirtualizeContentsView._blocksToRelease);
that._forceItemsBlocksInDOM(that.begin, end);
}
if (that._expandedRange.first.index < that.begin || that._expandedRange.last.index + 1 > that.end) {
if (zooming) {
info.setPromise(waitForSeZo(that._listView).then(function () {
return blocksCleanupWorker;
}));
} else {
info.setWork(blocksCleanupWorker);
}
} else {
complete();
}
}
job = Scheduler.schedule(blocksCleanupWorker, Scheduler.Priority.belowNormal, null, "WinJS.UI.ListView._lazilyRemoveRedundantItemsBlocks");
});
return promise.then(done, function (error) {
job.cancel();
that._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks canceled,info");
that._listView._writeProfilerMark("_lazilyRemoveRedundantItemsBlocks,StopTM");
return Promise.wrapError(error);
});
} else {
done();
return Promise.wrap();
}
});
},
_forceItemsBlocksInDOM: function VirtualizeContentsView_forceItemsBlocksInDOM(begin, end) {
if (!this._blockSize) {
return;
}
var perfId = "_forceItemsBlocksInDOM begin(" + begin + ") end(" + end + "),";
this._listView._writeProfilerMark(perfId + "StartTM");
var that = this,
added = 0,
removed = 0,
paddingProperty = "padding" + (this._listView._horizontal() ? "Left" : "Top");
function setPadder(itemsContainer, padding) {
var padder = itemsContainer.element.firstElementChild;
padder.style[paddingProperty] = padding;
}
function forEachBlock(callback) {
for (var g = 0; g < that.tree.length; g++) {
var itemsContainer = that.tree[g].itemsContainer;
for (var b = 0, len = itemsContainer.itemsBlocks.length; b < len; b++) {
if (callback(itemsContainer, itemsContainer.itemsBlocks[b])) {
return;
}
}
}
}
function measureItemsBlock(itemsBlock) {
that._listView._writeProfilerMark("_itemsBlockExtent,StartTM");
that._listView._itemsBlockExtent = utilities[that._listView._horizontal() ? "getTotalWidth" : "getTotalHeight"](itemsBlock.element);
that._listView._writeProfilerMark("_itemsBlockExtent(" + that._listView._itemsBlockExtent + "),info");
that._listView._writeProfilerMark("_itemsBlockExtent,StopTM");
}
function getItemsBlockExtent() {
if (that._listView._itemsBlockExtent === -1) {
// first try blocks already added to the DOM
forEachBlock(function (itemsContainer, itemsBlock) {
if (itemsBlock.items.length === that._blockSize && itemsBlock.element.parentNode === itemsContainer.element) {
measureItemsBlock(itemsBlock);
return true;
}
return false;
});
}
if (that._listView._itemsBlockExtent === -1) {
forEachBlock(function (itemsContainer, itemsBlock) {
if (itemsBlock.items.length === that._blockSize) {
itemsContainer.element.appendChild(itemsBlock.element);
measureItemsBlock(itemsBlock);
itemsContainer.element.removeChild(itemsBlock.element);
return true;
}
return false;
});
}
return that._listView._itemsBlockExtent;
}
function removeBlocks(itemsContainer, begin, end) {
function remove(blockIndex) {
var block = itemsContainer.itemsBlocks[blockIndex];
if (block && block.element.parentNode === itemsContainer.element) {
itemsContainer.element.removeChild(block.element);
removed++;
}
}
if (Array.isArray(begin)) {
begin.forEach(remove);
} else {
for (var i = begin; i < end; i++) {
remove(i);
}
}
}
function addBlocks(itemsContainer, begin, end) {
var padder = itemsContainer.element.firstElementChild,
previous = padder;
for (var i = begin; i < end; i++) {
var block = itemsContainer.itemsBlocks[i];
if (block) {
if (block.element.parentNode !== itemsContainer.element) {
itemsContainer.element.insertBefore(block.element, previous.nextElementSibling);
added++;
}
previous = block.element;
}
}
}
function collapseGroup(groupIndex) {
if (groupIndex < that.tree.length) {
that._listView._writeProfilerMark("collapseGroup(" + groupIndex + "),info");
var itemsContainer = that.tree[groupIndex].itemsContainer;
removeBlocks(itemsContainer, 0, itemsContainer.itemsBlocks.length);
setPadder(itemsContainer, "");
}
}
function expandGroup(groupIndex) {
if (groupIndex < that.tree.length) {
that._listView._writeProfilerMark("expandGroup(" + groupIndex + "),info");
var itemsContainer = that.tree[groupIndex].itemsContainer;
addBlocks(itemsContainer, 0, itemsContainer.itemsBlocks.length);
setPadder(itemsContainer, "");
}
}
function removedFromRange(oldRange, newRange) {
function expand(first, last) {
var array = [];
for (var i = first; i <= last; i++) {
array.push(i);
}
return array;
}
var newL = newRange[0];
var newR = newRange[1];
var oldL = oldRange[0];
var oldR = oldRange[1];
if (newR < oldL || newL > oldR) {
return expand(oldL, oldR);
} else if (newL > oldL && newR < oldR) {
return expand(oldL, newL - 1).concat(expand(newR + 1, oldR));
} else if (oldL < newL) {
return expand(oldL, newL - 1);
} else if (oldR > newR) {
return expand(newR + 1, oldR);
} else {
return null;
}
}
var firstGroupIndex = this._listView._groups.groupFromItem(begin),
lastGroupIndex = this._listView._groups.groupFromItem(end - 1);
var firstGroup = this._listView._groups.group(firstGroupIndex),
firstItemsContainer = that.tree[firstGroupIndex].itemsContainer;
var firstBlock = Math.floor((begin - firstGroup.startIndex) / this._blockSize);
var lastGroup = this._listView._groups.group(lastGroupIndex),
lastItemsContainer = that.tree[lastGroupIndex].itemsContainer;
var lastBlock = Math.floor((end - 1 - lastGroup.startIndex) / this._blockSize);
// if size of structure block is needed try to obtain it before modifying the tree to avoid a layout pass
if (firstBlock && that._listView._itemsBlockExtent === -1) {
forEachBlock(function (itemsContainer, itemsBlock) {
if (itemsBlock.items.length === that._blockSize && itemsBlock.element.parentNode === itemsContainer.element) {
measureItemsBlock(itemsBlock);
return true;
}
return false;
});
}
var groupsToCollapse = this._expandedRange ? removedFromRange([this._expandedRange.first.groupIndex, this._expandedRange.last.groupIndex], [firstGroupIndex, lastGroupIndex]) : null;
if (groupsToCollapse) {
groupsToCollapse.forEach(collapseGroup);
}
if (this._expandedRange && this._expandedRange.first.groupKey === firstGroup.key) {
var blocksToRemove = removedFromRange([this._expandedRange.first.block, Number.MAX_VALUE], [firstBlock, Number.MAX_VALUE]);
if (blocksToRemove) {
removeBlocks(firstItemsContainer, blocksToRemove);
}
} else if (this._expandedRange && firstGroupIndex >= this._expandedRange.first.groupIndex && firstGroupIndex <= this._expandedRange.last.groupIndex) {
removeBlocks(firstItemsContainer, 0, firstBlock);
}
if (firstGroupIndex !== lastGroupIndex) {
addBlocks(firstItemsContainer, firstBlock, firstItemsContainer.itemsBlocks.length);
addBlocks(lastItemsContainer, 0, lastBlock + 1);
} else {
addBlocks(firstItemsContainer, firstBlock, lastBlock + 1);
}
if (this._expandedRange && this._expandedRange.last.groupKey === lastGroup.key) {
var blocksToRemove = removedFromRange([0, this._expandedRange.last.block], [0, lastBlock]);
if (blocksToRemove) {
removeBlocks(lastItemsContainer, blocksToRemove);
}
} else if (this._expandedRange && lastGroupIndex >= this._expandedRange.first.groupIndex && lastGroupIndex <= this._expandedRange.last.groupIndex) {
removeBlocks(lastItemsContainer, lastBlock + 1, lastItemsContainer.itemsBlocks.length);
}
setPadder(firstItemsContainer, firstBlock ? firstBlock * getItemsBlockExtent() + "px" : "");
if (firstGroupIndex !== lastGroupIndex) {
setPadder(lastItemsContainer, "");
}
// groups between first and last
for (var i = firstGroupIndex + 1; i < lastGroupIndex; i++) {
expandGroup(i);
}
this._expandedRange = {
first: {
index: begin,
groupIndex: firstGroupIndex,
groupKey: firstGroup.key,
block: firstBlock
},
last: {
index: end - 1,
groupIndex: lastGroupIndex,
groupKey: lastGroup.key,
block: lastBlock
},
};
this._listView._writeProfilerMark("_forceItemsBlocksInDOM groups(" + firstGroupIndex + "-" + lastGroupIndex + ") blocks(" + firstBlock + "-" + lastBlock + ") added(" + added + ") removed(" + removed + "),info");
this._listView._writeProfilerMark(perfId + "StopTM");
},
_realizePageImpl: function VirtualizeContentsView_realizePageImpl() {
var that = this;
var perfId = "realizePage(scrollPosition:" + this._scrollbarPos + " forceLayout:" + this._forceRelayout + ")";
this._listView._writeProfilerMark(perfId + ",StartTM");
// It's safe to skip realizePage, so we just queue up the last request to run when the version manager
// get unlocked.
//
if (this._listView._versionManager.locked) {
this._listView._versionManager.unlocked.done(function () {
if (!that._listView._isZombie()) {
that._listView._batchViewUpdates(WinJS.UI._ViewChange.realize, WinJS.UI._ScrollToPriority.low, that._listView.scrollPosition);
}
});
this._listView._writeProfilerMark(perfId + ",StopTM");
return Promise.cancel;
}
return new Promise(function (c) {
var renderingCompleteSignal = new WinJS._Signal();
function complete() {
c();
renderingCompleteSignal.complete();
}
function viewPortPageRealized() {
that._listView._hideProgressBar();
that._state.setLoadingState(that._listView._LoadingState.viewPortLoaded);
if (that._executeAnimations) {
that._setState(RealizingAnimatingState, renderingCompleteSignal.promise);
}
}
function pageRealized(count) {
that._updateAriaMarkers(count === 0, that.firstIndexDisplayed, that.lastIndexDisplayed);
that._state.setLoadingState && that._state.setLoadingState(that._listView._LoadingState.itemsLoaded);
}
function finish(count) {
that._listView._clearInsertedItems();
that._listView._groups.removeElements();
viewPortPageRealized();
pageRealized(count);
complete();
}
that._state.setLoadingState(that._listView._LoadingState.itemsLoading);
if (that._firstLayoutPass) {
that._listView._showProgressBar(that._listView._element, "50%", "50%");
}
var count = that.containers.length;
if (count) {
// While the zoom animation is played we want to minimize the # of pages
// being fetched to improve TtFF for SeZo scenarios
var pagesToPrefetch = WinJS.UI._VirtualizeContentsView._pagesToPrefetch;
if (that._listView._zooming) {
pagesToPrefetch = 0;
}
var viewportLength = that._listView._getViewportLength(),
beginningOffset = Math.max(0, that._scrollbarPos - pagesToPrefetch * viewportLength),
endingOffset = that._scrollbarPos + (1 + pagesToPrefetch) * viewportLength;
var range = that._listView._layout.itemsFromRange(beginningOffset, endingOffset - 1);
if ((range.firstIndex < 0 || range.firstIndex >= count) && (range.lastIndex < 0 || range.lastIndex >= count)) {
that.begin = -1;
that.end = -1;
that.firstIndexDisplayed = -1;
that.lastIndexDisplayed = -1;
finish(count);
} else {
var begin = utilities._clamp(range.firstIndex, 0, count - 1),
end = utilities._clamp(range.lastIndex + 1, 0, count);
var inView = that._listView._layout.itemsFromRange(that._scrollbarPos, that._scrollbarPos + viewportLength - 1),
firstInView = utilities._clamp(inView.firstIndex, 0, count - 1),
lastInView = utilities._clamp(inView.lastIndex, 0, count - 1);
if (that._realizationLevel === WinJS.UI._VirtualizeContentsView._realizationLevel.skip && !that.lastRealizePass && firstInView === that.firstIndexDisplayed && lastInView === that.lastIndexDisplayed) {
that.begin = begin;
that.end = begin + Object.keys(that.items._itemData).length;
that._updateHeaders(that._listView._canvas, that.begin, that.end).done(function () {
that.lastRealizePass = null;
finish(count);
});
} else if ((that._forceRelayout || begin !== that.begin || end !== that.end || firstInView !== that.firstIndexDisplayed || lastInView !== that.lastIndexDisplayed) && (begin < end) && (beginningOffset < endingOffset)) {
that._listView._writeProfilerMark("realizePage currentInView(" + firstInView + "-" + lastInView + ") previousInView(" + that.firstIndexDisplayed + "-" + that.lastIndexDisplayed + ") change(" + (firstInView - that.firstIndexDisplayed) + "),info");
that._cancelRealize();
// cancelRealize changes the realizePass and resets begin/end
var currentPass = that._realizePass;
that.begin = begin;
that.end = end;
that.firstIndexDisplayed = firstInView;
that.lastIndexDisplayed = lastInView;
that.deletesWithoutRealize = 0;
that._ensureItemsBlocksInDOM(that.begin, that.end);
var realizeWork = that._realizeItems(
that._listView._itemCanvas,
that.begin,
that.end,
count,
currentPass,
that._scrollbarPos,
that._direction,
firstInView,
lastInView,
that._forceRelayout);
that._forceRelayout = false;
var realizePassWork = realizeWork.viewportItemsRealized.then(function () {
viewPortPageRealized();
return realizeWork.allItemsRealized;
}).then(function () {
if (that._realizePass === currentPass) {
return that._updateHeaders(that._listView._canvas, that.begin, that.end).then(function () {
pageRealized(count);
});
}
}).then(function () {
return realizeWork.loadingCompleted;
}).then(
function () {
that._unrealizeExcessiveItems();
that.lastRealizePass = null;
complete();
},
function (e) {
if (that._realizePass === currentPass) {
that.lastRealizePass = null;
that.begin = -1;
that.end = -1;
}
return WinJS.Promise.wrapError(e);
}
);
that.lastRealizePass = Promise.join([realizeWork.viewportItemsRealized, realizeWork.allItemsRealized, realizeWork.loadingCompleted, realizePassWork]);
that._unrealizeExcessiveItems();
} else if (!that.lastRealizePass) {
// We are currently in the "itemsLoading" state and need to get back to "complete". The
// previous realize pass has been completed so proceed to the other states.
finish(count);
} else {
that.lastRealizePass.then(complete);
}
}
} else {
that.begin = -1;
that.end = -1;
that.firstIndexDisplayed = -1;
that.lastIndexDisplayed = -1;
finish(count);
}
that._reportElementsLevel(that._direction);
that._listView._writeProfilerMark(perfId + ",StopTM");
});
},
realizePage: function VirtualizeContentsView_realizePage(scrollToFunctor, forceRelayout, scrollEndPromise, StateType) {
this._scrollToFunctor = makeFunctor(scrollToFunctor);
this._forceRelayout = this._forceRelayout || forceRelayout;
this._scrollEndPromise = scrollEndPromise;
this._listView._writeProfilerMark(this._state.name + "_realizePage,info");
this._state.realizePage(StateType || RealizingState);
},
onScroll: function VirtualizeContentsView_onScroll(scrollToFunctor, scrollEndPromise) {
this.realizePage(scrollToFunctor, false, scrollEndPromise, ScrollingState);
},
reload: function VirtualizeContentsView_reload(scrollToFunctor, highPriority) {
if (this._listView._isZombie()) { return; }
this._scrollToFunctor = makeFunctor(scrollToFunctor);
this._forceRelayout = true;
this._highPriorityRealize = !!highPriority;
this.stopWork(true);
this._listView._writeProfilerMark(this._state.name + "_rebuildTree,info");
this._state.rebuildTree();
},
refresh: function VirtualizeContentsView_refresh(scrollToFunctor) {
if (this._listView._isZombie()) { return; }
this._scrollToFunctor = makeFunctor(scrollToFunctor);
this._forceRelayout = true;
this._highPriorityRealize = true;
this.stopWork();
this._listView._writeProfilerMark(this._state.name + "_relayout,info");
this._state.relayout();
},
waitForValidScrollPosition: function VirtualizeContentsView_waitForValidScrollPosition(newPosition) {
var that = this;
var currentMaxScroll = this._listView._viewport[this._listView._scrollLength] - this._listView._getViewportLength();
if (newPosition > currentMaxScroll) {
return that._listView._itemsCount().then(function (count) {
// Wait until we have laid out enough containers to be able to set the scroll position to newPosition
if (that.containers.length < count) {
return Promise._cancelBlocker(that._creatingContainersWork && that._creatingContainersWork.promise).then(function () {
return that._getLayoutCompleted();
}).then(function () {
return newPosition;
});
} else {
return newPosition;
}
});
} else {
return WinJS.Promise.wrap(newPosition);
}
},
waitForEntityPosition: function VirtualizeContentsView_waitForEntityPosition(entity) {
var that = this;
this._listView._writeProfilerMark(this._state.name + "_waitForEntityPosition" + "(" + entity.type + ": " + entity.index + ")" + ",info");
return Promise._cancelBlocker(this._state.waitForEntityPosition(entity).then(function () {
if ((entity.type !== WinJS.UI.ObjectType.groupHeader && entity.index >= that.containers.length) ||
(entity.type === WinJS.UI.ObjectType.groupHeader && that._listView._groups.group(entity.index).startIndex >= that.containers.length)) {
return that._creatingContainersWork && that._creatingContainersWork.promise;
}
}).then(function () {
return that._getLayoutCompleted();
}));
},
stopWork: function VirtualizeContentsView_stopWork(stopTreeCreation) {
this._listView._writeProfilerMark(this._state.name + "_stop,info");
this._state.stop(stopTreeCreation);
if (this._layoutWork) {
this._layoutWork.cancel();
}
if (stopTreeCreation && this._creatingContainersWork) {
this._creatingContainersWork.cancel();
}
if (stopTreeCreation) {
this._state = new CreatedState(this);
}
},
_cancelRealize: function VirtualizeContentsView_cancelRealize() {
this._listView._writeProfilerMark("_cancelRealize,StartTM");
if (this.lastRealizePass || this.deferTimeout) {
this._forceRelayout = true;
}
this._clearDeferTimeout();
this._realizePass++;
if (this._headerRenderPromises) {
this._headerRenderPromises.cancel();
this._headerRenderPromises = null;
}
var last = this.lastRealizePass;
if (last) {
this.lastRealizePass = null;
this.begin = -1;
this.end = -1;
last.cancel();
}
this._listView._writeProfilerMark("_cancelRealize,StopTM");
},
resetItems: function VirtualizeContentsView_resetItems(unparent) {
if (!this._listView._isZombie()) {
this.firstIndexDisplayed = -1;
this.lastIndexDisplayed = -1;
this._runningAnimations = null;
this._executeAnimations = false;
var listView = this._listView;
this._firstLayoutPass = true;
listView._unsetFocusOnItem();
if (listView._currentMode().onDataChanged) {
listView._currentMode().onDataChanged();
}
this.items.each(function (index, item) {
if (unparent && item.parentNode && item.parentNode.parentNode) {
item.parentNode.parentNode.removeChild(item.parentNode);
}
listView._itemsManager.releaseItem(item);
WinJS.Utilities._disposeElement(item);
});
this.items.removeItems();
this._deferredReparenting = [];
if (unparent) {
listView._groups.removeElements();
}
listView._clearInsertedItems();
}
},
reset: function VirtualizeContentsView_reset() {
this.stopWork(true);
this._state = new CreatedState(this);
this.resetItems();
// when in the zombie state, we let disposal cleanup the ScrollView state
//
if (!this._listView._isZombie()) {
var listView = this._listView;
listView._groups.resetGroups();
listView._resetCanvas();
this.tree = null;
this.keyToGroupIndex = null;
this.containers = null;
this._expandedRange = null;
}
},
cleanUp: function VirtualizeContentsView_cleanUp() {
this.stopWork(true);
this._runningAnimations && this._runningAnimations.cancel();
var itemsManager = this._listView._itemsManager;
this.items.each(function (index, item) {
itemsManager.releaseItem(item);
WinJS.Utilities._disposeElement(item);
});
this._listView._unsetFocusOnItem();
this.items.removeItems();
this._deferredReparenting = [];
this._listView._groups.resetGroups();
this._listView._resetCanvas();
this.tree = null;
this.keyToGroupIndex = null;
this.containers = null;
this._expandedRange = null;
this.destroyed = true;
},
getContainer: function VirtualizeContentsView_getContainer(itemIndex) {
return this.containers[itemIndex];
},
_getHeaderContainer: function VirtualizeContentsView_getHeaderContainer(groupIndex) {
return this.tree[groupIndex].header;
},
_getGroups: function VirtualizeContentsView_getGroups(count) {
if (this._listView._groupDataSource) {
var groupsContainer = this._listView._groups.groups,
groups = [];
if (count) {
for (var i = 0, len = groupsContainer.length; i < len; i++) {
var group = groupsContainer[i],
nextStartIndex = i + 1 < len ? groupsContainer[i + 1].startIndex : count;
groups.push({
key: group.key,
size: nextStartIndex - group.startIndex
});
}
}
return groups;
} else {
return [{ key: "-1", size: count }];
}
},
_createChunk: function VirtualizeContentsView_createChunk(groups, count, chunkSize) {
var that = this;
this._listView._writeProfilerMark("createChunk,StartTM");
function addToGroup(itemsContainer, groupSize) {
var children = itemsContainer.element.children,
oldSize = children.length,
toAdd = Math.min(groupSize - itemsContainer.items.length, chunkSize);
utilities.insertAdjacentHTMLUnsafe(itemsContainer.element, "beforeend", WinJS.UI._repeat("", toAdd));
for (var i = 0; i < toAdd; i++) {
var container = children[oldSize + i];
itemsContainer.items.push(container);
that.containers.push(container);
}
}
function newGroup(group) {
var node = {
header: that._listView._groupDataSource ? that._createHeaderContainer() : null,
itemsContainer: {
element: that._createItemsContainer(),
items: []
}
};
that.tree.push(node);
that.keyToGroupIndex[group.key] = that.tree.length - 1;
addToGroup(node.itemsContainer, group.size);
}
if (this.tree.length && this.tree.length <= groups.length) {
var last = this.tree[this.tree.length - 1],
finalSize = groups[this.tree.length - 1].size;
// check if the last group in the tree already has all items. If not add items to this group
if (last.itemsContainer.items.length < finalSize) {
addToGroup(last.itemsContainer, finalSize);
this._listView._writeProfilerMark("createChunk,StopTM");
return;
}
}
if (this.tree.length < groups.length) {
newGroup(groups[this.tree.length]);
}
this._listView._writeProfilerMark("createChunk,StopTM");
},
_createChunkWithBlocks: function VirtualizeContentsView_createChunkWithBlocks(groups, count, blockSize, chunkSize) {
var that = this;
this._listView._writeProfilerMark("createChunk,StartTM");
function addToGroup(itemsContainer, toAdd) {
var lastExistingBlock = itemsContainer.itemsBlocks.length ? itemsContainer.itemsBlocks[itemsContainer.itemsBlocks.length - 1] : null;
if (lastExistingBlock && lastExistingBlock.items.length < blockSize) {
var fix = Math.min(toAdd, blockSize - lastExistingBlock.items.length);
utilities.insertAdjacentHTMLUnsafe(lastExistingBlock.element, "beforeend", WinJS.UI._repeat("", fix));
var oldSize = lastExistingBlock.items.length;
children = lastExistingBlock.element.children;
for (var j = 0; j < fix; j++) {
var child = children[oldSize + j];
lastExistingBlock.items.push(child);
that.containers.push(child);
}
toAdd -= fix;
}
if (toAdd > chunkSize) {
toAdd = Math.min(toAdd, Math.max(1, Math.floor(chunkSize / blockSize)) * blockSize);
}
var blocks = Math.floor(toAdd / blockSize),
lastBlockSize = toAdd % blockSize;
var blockMarkup = "
";
blocks++;
}
var blocksTemp = document.createElement("div");
utilities.setInnerHTMLUnsafe(blocksTemp, markup);
var children = blocksTemp.children;
for (var i = 0; i < blocks; i++) {
var block = children[i],
blockNode = {
element: block,
items: WinJS.UI._nodeListToArray(block.children)
};
itemsContainer.itemsBlocks.push(blockNode);
for (var n = 0; n < blockNode.items.length; n++) {
that.containers.push(blockNode.items[n]);
}
}
}
function newGroup(group) {
var node = {
header: that._listView._groupDataSource ? that._createHeaderContainer() : null,
itemsContainer: {
element: that._createItemsContainer(),
itemsBlocks: []
}
};
that.tree.push(node);
that.keyToGroupIndex[group.key] = that.tree.length - 1;
addToGroup(node.itemsContainer, group.size);
}
if (this.tree.length && this.tree.length <= groups.length) {
var lastContainer = this.tree[this.tree.length - 1].itemsContainer,
finalSize = groups[this.tree.length - 1].size,
currentSize = 0;
if (lastContainer.itemsBlocks.length) {
currentSize = (lastContainer.itemsBlocks.length - 1) * blockSize + lastContainer.itemsBlocks[lastContainer.itemsBlocks.length - 1].items.length;
}
if (currentSize < finalSize) {
addToGroup(lastContainer, finalSize - currentSize);
this._listView._writeProfilerMark("createChunk,StopTM");
return;
}
}
if (this.tree.length < groups.length) {
newGroup(groups[this.tree.length]);
}
this._listView._writeProfilerMark("createChunk,StopTM");
},
_generateCreateContainersWorker: function VirtualizeContentsView_generateCreateContainersWorker() {
var that = this,
counter = 0,
skipWait = false;
return function work(info) {
if (!that._listView._versionManager.locked) {
that._listView._itemsCount().then(function (count) {
var zooming = !skipWait && shouldWaitForSeZo(that._listView);
if (!zooming) {
if (that._listView._isZombie()) { return; }
skipWait = false;
var end = performance.now() + _VirtualizeContentsView._createContainersJobTimeslice,
groups = that._getGroups(count),
startLength = that.containers.length,
realizedToEnd = that.end === that.containers.length,
chunkSize = WinJS.UI._VirtualizeContentsView._chunkSize;
do {
that._blockSize ? that._createChunkWithBlocks(groups, count, that._blockSize, chunkSize) : that._createChunk(groups, count, chunkSize);
counter++;
} while (that.containers.length < count && performance.now() < end);
that._listView._writeProfilerMark("createContainers yields containers(" + that.containers.length + "),info");
that._listView._affectedRange.add({ start: startLength, end: that.containers.length }, count);
if (realizedToEnd) {
that.stopWork();
that._listView._writeProfilerMark(that._state.name + "_relayout,info");
that._state.relayout();
} else {
that._listView._writeProfilerMark(that._state.name + "_layoutNewContainers,info");
that._state.layoutNewContainers();
}
if (that.containers.length < count) {
info.setWork(work);
} else {
that._listView._writeProfilerMark("createContainers completed steps(" + counter + "),info");
that._creatingContainersWork.complete();
}
} else {
// Waiting on zooming
info.setPromise(waitForSeZo(that._listView).then(function (timedOut) {
skipWait = timedOut;
return work;
}));
}
});
} else {
// Version manager locked
info.setPromise(that._listView._versionManager.unlocked.then(function () {
return work;
}));
}
};
},
_scheduleLazyTreeCreation: function VirtualizeContentsView_scheduleLazyTreeCreation() {
return utilities.Scheduler.schedule(this._generateCreateContainersWorker(), utilities.Scheduler.Priority.idle, this, "WinJS.UI.ListView.LazyTreeCreation");
},
_createContainers: function VirtualizeContentsView_createContainers() {
this.tree = null;
this.keyToGroupIndex = null;
this.containers = null;
this._expandedRange = null;
var that = this,
count;
return this._listView._itemsCount().then(function (c) {
if (c === 0) {
that._listView._hideProgressBar();
}
count = c;
that._listView._writeProfilerMark("createContainers(" + count + "),StartTM");
if (that._listView._groupDataSource) {
return that._listView._groups.initialize();
}
}).then(function () {
that._listView._writeProfilerMark("numberOfItemsPerItemsBlock,StartTM");
return (count && that._listView._groups.length() ? that._listView._layout.numberOfItemsPerItemsBlock : null);
}).then(function (blockSize) {
that._listView._writeProfilerMark("numberOfItemsPerItemsBlock(" + blockSize + "),info");
that._listView._writeProfilerMark("numberOfItemsPerItemsBlock,StopTM");
that._listView._resetCanvas();
that.tree = [];
that.keyToGroupIndex = {};
that.containers = [];
that._blockSize = blockSize;
var groups = that._getGroups(count);
var end = performance.now() + WinJS.UI._VirtualizeContentsView._maxTimePerCreateContainers,
chunkSize = Math.min(WinJS.UI._VirtualizeContentsView._startupChunkSize, WinJS.UI._VirtualizeContentsView._chunkSize);
do {
var stop = blockSize ? that._createChunkWithBlocks(groups, count, blockSize, chunkSize) : that._createChunk(groups, count, chunkSize);
} while (performance.now() < end && that.containers.length < count && !stop);
that._listView._writeProfilerMark("createContainers created(" + that.containers.length + "),info");
that._listView._affectedRange.add({ start: 0, end: that.containers.length }, count);
if (that.containers.length < count) {
var jobNode = that._scheduleLazyTreeCreation();
that._creatingContainersWork.promise.done(null, function () {
jobNode.cancel();
});
} else {
that._listView._writeProfilerMark("createContainers completed synchronously,info");
that._creatingContainersWork.complete();
}
that._listView._writeProfilerMark("createContainers(" + count + "),StopTM");
});
},
_updateItemsBlocks: function VirtualizeContentsView_updateItemsBlocks(blockSize) {
var that = this;
var usingStructuralNodes = !!blockSize;
function createNewBlock() {
var element = document.createElement("div");
element.className = WinJS.UI._itemsBlockClass;
return element;
}
function updateGroup(itemsContainer, startIndex) {
var blockElements = [],
itemsCount = 0,
blocks = itemsContainer.itemsBlocks,
b;
function rebuildItemsContainer() {
itemsContainer.itemsBlocks = null;
itemsContainer.items = [];
for (var i = 0; i < itemsCount; i++) {
var container = that.containers[startIndex + i];
itemsContainer.element.appendChild(container);
itemsContainer.items.push(container);
}
}
function rebuildItemsContainerWithBlocks() {
itemsContainer.itemsBlocks = [{
element: blockElements.length ? blockElements.shift() : createNewBlock(),
items: []
}];
var currentBlock = itemsContainer.itemsBlocks[0];
for (var i = 0; i < itemsCount; i++) {
if (currentBlock.items.length === blockSize) {
var nextBlock = blockElements.length ? blockElements.shift() : createNewBlock();
itemsContainer.itemsBlocks.push({
element: nextBlock,
items: []
});
currentBlock = itemsContainer.itemsBlocks[itemsContainer.itemsBlocks.length - 1];
}
var container = that.containers[startIndex + i];
currentBlock.element.appendChild(container);
currentBlock.items.push(container);
}
itemsContainer.items = null;
}
if (blocks) {
for (b = 0; b < blocks.length; b++) {
itemsCount += blocks[b].items.length;
blockElements.push(blocks[b].element);
}
} else {
itemsCount = itemsContainer.items.length;
}
if (usingStructuralNodes) {
rebuildItemsContainerWithBlocks();
} else {
rebuildItemsContainer();
}
for (b = 0; b < blockElements.length; b++) {
var block = blockElements[b];
if (block.parentNode === itemsContainer.element) {
itemsContainer.element.removeChild(block);
}
}
return itemsCount;
}
for (var g = 0, startIndex = 0; g < this.tree.length; g++) {
startIndex += updateGroup(this.tree[g].itemsContainer, startIndex);
}
that._blockSize = blockSize;
},
_layoutItems: function VirtualizeContentsView_layoutItems() {
var that = this;
return this._listView._itemsCount().then(function (count) {
return Promise.as(that._listView._layout.numberOfItemsPerItemsBlock).then(function (blockSize) {
that._listView._writeProfilerMark("numberOfItemsPerItemsBlock(" + blockSize + "),info");
if (blockSize !== that._blockSize) {
that._updateItemsBlocks(blockSize);
that._listView._itemsBlockExtent = -1;
}
var affectedRange = that._listView._affectedRange.get();
var changedRange;
// We accumulate all changes that occur between layouts in _affectedRange. If layout is interrupted due to additional
// modifications, _affectedRange will become the union of the previous range of changes and the new range of changes
// and will be passed to layout again. _affectedRange is reset whenever layout completes.
if (affectedRange) {
changedRange = {
// _affectedRange is stored in the format [start, end), layout expects a range in the form of [firstIndex , lastIndex]
// To ensure that layout can successfully use the expected range to find all of the groups which need to be re-laid out
// we will pad an extra index at the front end such that layout receives [start - 1, end] in form of [lastIndex, firstIndex].
firstIndex: Math.max(affectedRange.start - 1, 0),
lastIndex: Math.min(that.containers.length - 1, affectedRange.end) // Account for any constrained upper limits from lazily loaded win-container's.
};
if (changedRange.firstIndex < that.containers.length || that.containers.length === 0) {
return that._listView._layout.layout(that.tree, changedRange,
that._modifiedElements || [], that._modifiedGroups || []);
}
}
// There is nothing to layout.
that._listView._affectedRange.clear();
return {
realizedRangeComplete: Promise.wrap(),
layoutComplete: Promise.wrap()
};
});
});
},
updateTree: function VirtualizeContentsView_updateTree(count, delta, modifiedElements) {
this._listView._writeProfilerMark(this._state.name + "_updateTree,info");
return this._state.updateTree(count, delta, modifiedElements);
},
_updateTreeImpl: function VirtualizeContentsView_updateTreeImpl(count, delta, modifiedElements, skipUnrealizeItems) {
this._executeAnimations = true;
this._modifiedElements = modifiedElements;
if (modifiedElements.handled) {
return;
}
modifiedElements.handled = true;
this._listView._writeProfilerMark("_updateTreeImpl,StartTM");
var that = this,
i;
if (!skipUnrealizeItems) {
// If we skip unrealize items, this work will eventually happen when we reach the UnrealizingState. Sometimes,
// it is appropriate to defer the unrealize work in order to optimize scenarios (e.g, edits that happen when we are
// in the CompletedState, that way the animation can start sooner).
this._unrealizeItems();
}
function removeElements(array) {
for (var i = 0, len = array.length; i < len; i++) {
var itemBox = array[i];
itemBox.parentNode.removeChild(itemBox);
}
}
for (var i = 0, len = modifiedElements.length; i < len; i++) {
if (modifiedElements[i]._itemBox && modifiedElements[i]._itemBox.parentNode) {
utilities.removeClass(modifiedElements[i]._itemBox.parentNode, WinJS.UI._selectedClass);
}
}
this.items.each(function (index, item, itemData) {
itemData.container && utilities.removeClass(itemData.container, WinJS.UI._selectedClass);
itemData.container && utilities.addClass(itemData.container, WinJS.UI._backdropClass);
});
var removedGroups = this._listView._updateContainers(this._getGroups(count), count, delta, modifiedElements);
removeElements(removedGroups.removedHeaders);
removeElements(removedGroups.removedItemsContainers);
for (var i = 0, len = modifiedElements.length; i < len; i++) {
var modifiedElement = modifiedElements[i];
if (modifiedElement.newIndex !== -1) {
modifiedElement.element = this.getContainer(modifiedElement.newIndex);
if (!modifiedElement.element) {
throw "Container missing after updateContainers.";
}
} else {
utilities.removeClass(modifiedElement.element, WinJS.UI._backdropClass);
}
}
// We only need to restore focus if the current focus is within surface
var activeElement = document.activeElement;
if (this._listView._canvas.contains(activeElement)) {
this._requireFocusRestore = activeElement
}
this._deferredReparenting = [];
this.items.each(function (index, item, itemData) {
var container = that.getContainer(index),
itemBox = itemData.itemBox;
if (itemBox && container) {
if (itemBox.parentNode !== container) {
if (index >= that.firstIndexDisplayed && index <= that.lastIndexDisplayed) {
that._appendAndRestoreFocus(container, itemBox);
} else {
that._deferredReparenting.push({ itemBox: itemBox, container: container });
}
}
utilities.removeClass(container, WinJS.UI._backdropClass);
itemData.container = container;
utilities[that._listView.selection._isIncluded(index) ? "addClass" : "removeClass"](container, WinJS.UI._selectedClass);
if (!that._listView.selection._isIncluded(index) && utilities.hasClass(itemBox, WinJS.UI._selectedClass)) {
WinJS.UI._ItemEventsHandler.renderSelection(itemBox, itemData.element, false, true);
}
}
});
this._listView._writeProfilerMark("_updateTreeImpl,StopTM");
},
_completeUpdateTree: function () {
if (this._deferredReparenting) {
var deferredCount = this._deferredReparenting.length;
if (deferredCount > 0) {
var perfId = "_completeReparenting(" + deferredCount + ")";
this._listView._writeProfilerMark(perfId + ",StartTM");
var deferredItem;
for (var i = 0; i < deferredCount; i++) {
deferredItem = this._deferredReparenting[i];
this._appendAndRestoreFocus(deferredItem.container, deferredItem.itemBox);
}
this._deferredReparenting = [];
this._listView._writeProfilerMark(perfId + ",StopTM");
}
}
this._requireFocusRestore = null;
},
_appendAndRestoreFocus: function VirtualizeContentsView_appendAndRestoreFocus(container, itemBox) {
if (itemBox.parentNode !== container) {
var activeElement;
if (this._requireFocusRestore) {
activeElement = document.activeElement;
}
if (this._requireFocusRestore && this._requireFocusRestore === activeElement && (container.contains(activeElement) || itemBox.contains(activeElement))) {
this._listView._unsetFocusOnItem();
activeElement = document.activeElement;
}
utilities.empty(container);
container.appendChild(itemBox);
if (this._requireFocusRestore && activeElement === this._listView._keyboardEventsHelper) {
var focused = this._listView._selection._getFocused();
if (focused.type === WinJS.UI.ObjectType.item && this.items.itemBoxAt(focused.index) === itemBox) {
try {
this._requireFocusRestore.setActive();
} catch (e) {
}
this._requireFocusRestore = null;
}
}
}
},
_startAnimations: function VirtualizeContentsView_startAnimations() {
this._listView._writeProfilerMark("startAnimations,StartTM");
var that = this;
this._hasAnimationInViewportPending = false;
var animationPromise = Promise.as(this._listView._layout.executeAnimations()).then(function () {
that._listView._writeProfilerMark("startAnimations,StopTM");
});
return animationPromise;
},
_setState: function VirtualizeContentsView_setState(NewStateType, arg) {
if (!this._listView._isZombie()) {
var prevStateName = this._state.name;
this._state = new NewStateType(this, arg);
this._listView._writeProfilerMark(this._state.name + "_enter from(" + prevStateName + "),info");
this._state.enter();
}
},
getAdjacent: function VirtualizeContentsView_getAdjacent(currentFocus, direction) {
var that = this;
return this.waitForEntityPosition(currentFocus).then(function () {
return that._listView._layout.getAdjacent(currentFocus, direction);
});
},
hitTest: function VirtualizeContentsView_hitTest(x, y) {
if (!this._realizedRangeLaidOut) {
var retVal = this._listView._layout.hitTest(x, y);
retVal.index = utilities._clamp(retVal.index, -1, this._listView._cachedCount - 1, 0);
retVal.insertAfterIndex = utilities._clamp(retVal.insertAfterIndex, -1, this._listView._cachedCount - 1, 0);
return retVal;
} else {
return {
index: -1,
insertAfterIndex: -1
};
};
},
_createTreeBuildingSignal: function VirtualizeContentsView__createTreeBuildingSignal() {
if (!this._creatingContainersWork) {
this._creatingContainersWork = new WinJS._Signal();
var that = this;
this._creatingContainersWork.promise.done(
function () {
that._creatingContainersWork = null;
},
function (error) {
that._creatingContainersWork = null;
}
);
}
},
_createLayoutSignal: function VirtualizeContentsView_createLayoutSignal() {
var that = this;
if (!this._layoutCompleted) {
this._layoutCompleted = new WinJS._Signal();
this._layoutCompleted.promise.done(
function () {
that._layoutCompleted = null;
},
function (error) {
that._layoutCompleted = null;
}
);
}
if (!this._realizedRangeLaidOut) {
this._realizedRangeLaidOut = new WinJS._Signal();
this._realizedRangeLaidOut.promise.done(
function () {
that._realizedRangeLaidOut = null;
},
function (error) {
that._realizedRangeLaidOut = null;
}
);
}
},
_getLayoutCompleted: function VirtualizeContentsView_getLayoutCompleted() {
return this._layoutCompleted ? WinJS.Promise._cancelBlocker(this._layoutCompleted.promise) : Promise.wrap();
},
_createSurfaceChild: function VirtualizeContentsView_createSurfaceChild(className, insertAfter) {
var element = document.createElement("div");
element.className = className;
this._listView._canvas.insertBefore(element, insertAfter ? insertAfter.nextElementSibling : null);
return element;
},
_executeScrollToFunctor: function VirtualizeContentsView_executeScrollToFunctor() {
var that = this;
return Promise.as(this._scrollToFunctor ? this._scrollToFunctor() : null).then(function (scroll) {
that._scrollToFunctor = null;
scroll = scroll || {};
// _scrollbarPos is initialized to 0 in the constructor, and we only set it when a valid integer
// value is passed in order to account for cases when there is not a _scrollToFunctor
if (+scroll.position === scroll.position) {
that._scrollbarPos = scroll.position;
}
that._direction = scroll.direction || "right";
});
}
};
function nop() { }
/*
View is in this state before reload is called so during startup, after datasource change etc.
*/
var CreatedState = WinJS.Class.define(function CreatedState_ctor(view) {
this.view = view;
this.view._createTreeBuildingSignal();
this.view._createLayoutSignal();
}, {
name: 'CreatedState',
enter: function CreatedState_enter() {
this.view._createTreeBuildingSignal();
this.view._createLayoutSignal();
},
stop: nop,
realizePage: nop,
rebuildTree: function CreatedState_rebuildTree() {
this.view._setState(BuildingState);
},
relayout: function CreatedState_relayout() {
this.view._setState(BuildingState);
},
layoutNewContainers: nop,
waitForEntityPosition: function CreatedState_waitForEntityPosition(entity) {
this.view._setState(BuildingState);
return this.view._getLayoutCompleted();
},
updateTree: nop
});
/*
In this state View is building its DOM tree with win-container element for each item in the data set.
To build the tree the view needs to know items count or for grouped case the count of groups and the
count of items in each group. The view enters this state when the tree needs to be built during
startup or rebuild after data source change and etc.
BuildingState => LayingoutState | CreatedState
*/
var BuildingState = WinJS.Class.define(function BuildingState_ctor(view) {
this.view = view;
}, {
name: 'BuildingState',
enter: function BuildingState_enter() {
this.canceling = false;
this.view._createTreeBuildingSignal();
this.view._createLayoutSignal();
var that = this;
// Use a signal to guarantee that this.promise is set before the promise
// handler is executed.
var promiseStoredSignal = new WinJS._Signal();
this.promise = promiseStoredSignal.promise.then(function () {
return that.view._createContainers();
}).then(
function () {
that.view._setState(LayingoutState);
},
function (error) {
if (!that.canceling) {
// this is coming from layout. ListView is hidden. We need to raise complete and wait in initial state for further actions
that.view._setState(CreatedState);
that.view._listView._raiseViewComplete();
}
return Promise.wrapError(error);
}
);
promiseStoredSignal.complete();
},
stop: function BuildingState_stop() {
this.canceling = true;
this.promise.cancel();
this.view._setState(CreatedState);
},
realizePage: nop,
rebuildTree: function BuildingState_rebuildTree() {
this.canceling = true;
this.promise.cancel();
this.enter();
},
relayout: nop,
layoutNewContainers: nop,
waitForEntityPosition: function BuildingState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: nop
});
/*
In this state View waits for the layout to lay out win-container elements. The view enters this state
after edits or resize.
LayingoutState => RealizingState | BuildingState | CanceledState | CompletedState | LayoutCanceledState
*/
var LayingoutState = WinJS.Class.define(function LayingoutState_ctor(view, NextStateType) {
this.view = view;
this.nextStateType = NextStateType || RealizingState;
}, {
name: 'LayingoutState',
enter: function LayingoutState_enter() {
var that = this;
this.canceling = false;
this.view._createLayoutSignal();
this.view._listView._writeProfilerMark(this.name + "_enter_layoutItems,StartTM");
// Use a signal to guarantee that this.promise is set before the promise
// handler is executed.
var promiseStoredSignal = new WinJS._Signal();
this.promise = promiseStoredSignal.promise.then(function () {
return that.view._layoutItems();
}).then(function (layoutPromises) {
// View is taking ownership of this promise and it will cancel it in stopWork
that.view._layoutWork = layoutPromises.layoutComplete;
return layoutPromises.realizedRangeComplete;
}).then(
function () {
that.view._listView._writeProfilerMark(that.name + "_enter_layoutItems,StopTM");
that.view._listView._clearInsertedItems();
that.view._setAnimationInViewportState(that.view._modifiedElements);
that.view._modifiedElements = [];
that.view._modifiedGroups = [];
that.view._realizedRangeLaidOut.complete();
that.view._layoutWork.then(function () {
that.view._listView._writeProfilerMark(that.name + "_enter_layoutCompleted,info");
that.view._listView._affectedRange.clear();
that.view._layoutCompleted.complete();
});
if (!that.canceling) {
that.view._setState(that.nextStateType);
}
},
function (error) {
that.view._listView._writeProfilerMark(that.name + "_enter_layoutCanceled,info");
if (!that.canceling) {
// Cancel is coming from layout itself so ListView is hidden or empty. In this case we want to raise loadingStateChanged
that.view.firstIndexDisplayed = that.view.lastIndexDisplayed = -1;
that.view._updateAriaMarkers(true, that.view.firstIndexDisplayed, that.view.lastIndexDisplayed);
that.view._setState(CompletedState);
}
return Promise.wrapError(error);
}
);
promiseStoredSignal.complete();
if (this.canceling) {
this.promise.cancel();
}
},
cancelLayout: function LayingoutState_cancelLayout(switchState) {
this.view._listView._writeProfilerMark(this.name + "_cancelLayout,info");
this.canceling = true;
if (this.promise) {
this.promise.cancel();
}
if (switchState) {
this.view._setState(LayoutCanceledState);
}
},
stop: function LayingoutState_stop() {
this.cancelLayout(true);
},
realizePage: nop,
rebuildTree: function LayingoutState_rebuildTree() {
this.cancelLayout(false);
this.view._setState(BuildingState);
},
relayout: function LayingoutState_relayout() {
this.cancelLayout(false);
this.enter();
},
layoutNewContainers: function LayingoutState_layoutNewContainers() {
this.relayout();
},
waitForEntityPosition: function LayingoutState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: function LayingoutState_updateTree(count, delta, modifiedElements) {
return this.view._updateTreeImpl(count, delta, modifiedElements);
}
});
/*
View enters this state when layout is canceled.
LayoutCanceledState => LayingoutState | BuildingState
*/
var LayoutCanceledState = WinJS.Class.define(function LayoutCanceledState_ctor(view) {
this.view = view;
}, {
name: 'LayoutCanceledState',
enter: nop,
stop: nop,
realizePage: function LayoutCanceledState_realizePage() {
this.relayout();
},
rebuildTree: function LayoutCanceledState_rebuildTree() {
this.view._setState(BuildingState);
},
relayout: function LayoutCanceledState_relayout() {
this.view._setState(LayingoutState);
},
layoutNewContainers: function LayoutCanceledState_layoutNewContainers() {
this.relayout();
},
waitForEntityPosition: function LayoutCanceledState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: function LayoutCanceledState_updateTree(count, delta, modifiedElements) {
return this.view._updateTreeImpl(count, delta, modifiedElements);
}
});
/*
Contents of items in the current viewport and prefetch area is realized during this stage.
The view enters this state when items needs to be realized for instance during initialization, edits and resize.
RealizingState => RealizingAnimatingState | UnrealizingState | LayingoutState | BuildingState | CanceledState
*/
var RealizingState = WinJS.Class.define(function RealizingState_ctor(view) {
this.view = view;
this.nextState = UnrealizingState;
this.relayoutNewContainers = true;
}, {
name: 'RealizingState',
enter: function RealizingState_enter() {
var that = this;
var promiseStoredSignal = new WinJS._Signal();
this.promise = promiseStoredSignal.promise.then(function () {
return that.view._executeScrollToFunctor();
}).then(function () {
that.relayoutNewContainers = false;
return Promise._cancelBlocker(that.view._realizePageImpl());
}).then(
function () {
if (that.view._state === that) {
that.view._completeUpdateTree();
that.view._listView._writeProfilerMark("RealizingState_to_UnrealizingState");
that.view._setState(that.nextState);
}
},
function (error) {
if (that.view._state === that && !that.canceling) {
that.view._listView._writeProfilerMark("RealizingState_to_CanceledState");
that.view._setState(CanceledState);
}
return Promise.wrapError(error);
}
);
promiseStoredSignal.complete();
},
stop: function RealizingState_stop() {
this.canceling = true;
this.promise.cancel();
this.view._cancelRealize();
this.view._setState(CanceledState);
},
realizePage: function RealizingState_realizePage() {
this.canceling = true;
this.promise.cancel();
this.enter();
},
rebuildTree: function RealizingState_rebuildTree() {
this.stop();
this.view._setState(BuildingState);
},
relayout: function RealizingState_relayout() {
this.stop();
this.view._setState(LayingoutState);
},
layoutNewContainers: function RealizingState_layoutNewContainers() {
if (this.relayoutNewContainers) {
this.relayout();
} else {
this.view._createLayoutSignal();
this.view._relayoutInComplete = true;
}
},
waitForEntityPosition: function RealizingState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: function RealizingState_updateTree(count, delta, modifiedElements) {
return this.view._updateTreeImpl(count, delta, modifiedElements);
},
setLoadingState: function RealizingState_setLoadingState(state) {
this.view._listView._setViewState(state);
}
});
/*
The view enters this state when the realize pass, animations or unrealizing was canceled or after newContainers have been laid out.
In this state view waits for the next call from ListViewImpl. It can be scroll, edit etc.
CanceledState => RealizingState | ScrollingState | LayingoutState | BuildingState
*/
var CanceledState = WinJS.Class.define(function CanceledState_ctor(view) {
this.view = view;
}, {
name: 'CanceledState',
enter: nop,
stop: function CanceledState_stop() {
// cancelRealize cancels ariaSetup which can still be in progress
this.view._cancelRealize();
},
realizePage: function CanceledState_realizePage(NewStateType) {
this.stop();
this.view._setState(NewStateType);
},
rebuildTree: function CanceledState_rebuildTree() {
this.stop();
this.view._setState(BuildingState);
},
relayout: function CanceledState_relayout(NextStateType) {
this.stop();
this.view._setState(LayingoutState, NextStateType);
},
layoutNewContainers: function CanceledState_layoutNewContainers() {
this.relayout(CanceledState);
},
waitForEntityPosition: function CanceledState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: function CanceledState_updateTree(count, delta, modifiedElements) {
return this.view._updateTreeImpl(count, delta, modifiedElements);
}
});
/*
This state is almost identical with RealizingState. Currently the difference is that in this state loadingStateChanged events aren’t
raised and after complete the state is switched to ScrollingPausedState to wait until end of scrolling.
ScrollingState => RealizingAnimatingState | ScrollingPausedState | LayingoutState | BuildingState | CanceledState
*/
var ScrollingState = WinJS.Class.derive(RealizingState, function ScrollingState_ctor(view) {
this.view = view;
this.nextState = ScrollingPausedState;
this.relayoutNewContainers = true;
}, {
name: 'ScrollingState',
setLoadingState: function ScrollingState_setLoadingState(state) {
}
});
/*
The view waits in this state for end of scrolling which for touch is signaled by MSManipulationStateChanged event and for mouse it is timeout.
ScrollingPausedState => RealizingAnimatingState | ScrollingPausedState | LayingoutState | BuildingState | CanceledState
*/
var ScrollingPausedState = WinJS.Class.derive(CanceledState, function ScrollingPausedState_ctor(view) {
this.view = view;
}, {
name: 'ScrollingPausedState',
enter: function ScrollingPausedState_enter() {
var that = this;
this.promise = Promise._cancelBlocker(this.view._scrollEndPromise).then(function () {
that.view._setState(UnrealizingState);
});
},
stop: function ScrollingPausedState_stop() {
this.promise.cancel();
// cancelRealize cancels ariaSetup which can still be in progress
this.view._cancelRealize();
},
});
/*
In this state, view unrealizes not needed items and then waits for all renderers to complete.
UnrealizingState => CompletedState | RealizingState | ScrollingState | LayingoutState | BuildingState | CanceledState
*/
var UnrealizingState = WinJS.Class.define(function UnrealizingState_ctor(view) {
this.view = view;
}, {
name: 'UnrealizingState',
enter: function UnrealizingState_enter() {
var that = this;
this.promise = this.view._lazilyUnrealizeItems().then(function () {
that.view._listView._writeProfilerMark("_renderCompletePromise wait starts,info");
return that.view._renderCompletePromise;
}).then(function () {
that.view._setState(CompletedState);
});
},
stop: function UnrealizingState_stop() {
// cancelRealize cancels ariaSetup which can still be in progress
this.view._cancelRealize();
this.promise.cancel();
this.view._setState(CanceledState);
},
realizePage: function UnrealizingState_realizePage(NewStateType) {
this.promise.cancel();
this.view._setState(NewStateType);
},
rebuildTree: function UnrealizingState_rebuildTree() {
this.view._setState(BuildingState);
},
relayout: function UnrealizingState_relayout() {
this.view._setState(LayingoutState);
},
layoutNewContainers: function UnrealizingState_layoutNewContainers() {
this.view._createLayoutSignal();
this.view._relayoutInComplete = true;
},
waitForEntityPosition: function UnrealizingState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: function UnrealizingState_updateTree(count, delta, modifiedElements) {
return this.view._updateTreeImpl(count, delta, modifiedElements);
}
});
/*
We enter this state, when there are animations to execute, and we have already fired the viewportloaded event
RealizingAnimatingState => RealizingState | UnrealizingState | LayingoutState | BuildingState | CanceledState
*/
var RealizingAnimatingState = WinJS.Class.define(function RealizingStateAnimating_ctor(view, realizePromise) {
this.view = view;
this.realizePromise = realizePromise;
this.realizeId = 1;
}, {
name: 'RealizingAnimatingState',
enter: function RealizingAnimatingState_enter() {
var that = this;
this.animating = true;
this.animatePromise = this.view._startAnimations();
this.animateSignal = new WinJS._Signal();
this.view._executeAnimations = false;
this.animatePromise.done(
function () {
that.animating = false;
if (that.modifiedElements) {
that.view._updateTreeImpl(that.count, that.delta, that.modifiedElements);
that.modifiedElements = null;
that.view._setState(CanceledState);
} else {
that.animateSignal.complete();
}
}, function (error) {
that.animating = false;
return Promise.wrapError(error);
}
);
this._waitForRealize();
},
_waitForRealize: function RealizingAnimatingState_waitForRealize() {
var that = this;
this.realizing = true;
this.realizePromise.done(function () {
that.realizing = false;
});
var currentRealizeId = ++this.realizeId;
Promise.join([this.realizePromise, this.animateSignal.promise]).done(function () {
if (currentRealizeId === that.realizeId) {
that.view._completeUpdateTree();
that.view._listView._writeProfilerMark("RealizingAnimatingState_to_UnrealizingState");
that.view._setState(UnrealizingState);
}
});
},
stop: function RealizingAnimatingState_stop(stopTreeCreation) {
// always cancel realization
this.realizePromise.cancel();
this.view._cancelRealize();
// animations are canceled only when tree needs to be rebuilt
if (stopTreeCreation) {
this.animatePromise.cancel();
this.view._setState(CanceledState);
}
},
realizePage: function RealizingAnimatingState_realizePage() {
if (!this.modifiedElements) {
var that = this;
this.realizePromise = this.view._executeScrollToFunctor().then(function () {
return Promise._cancelBlocker(that.view._realizePageImpl());
});
this._waitForRealize();
}
},
rebuildTree: function RealizingAnimatingState_rebuildTree() {
this.stop(true);
this.view._setState(BuildingState);
},
relayout: function RealizingAnimatingState_relayout() {
// Relayout caused by edits should be stopped by updateTree but relayout can be caused by resize or containers creation and in these cases we should stop animations
this.stop(true);
// if tree update was waiting for animations we should do it now
if (this.modifiedElements) {
this.view._updateTreeImpl(this.count, this.delta, this.modifiedElements);
this.modifiedElements = null;
}
this.view._setState(LayingoutState);
},
layoutNewContainers: function RealizingAnimatingState_layoutNewContainers() {
this.view._createLayoutSignal();
this.view._relayoutInComplete = true;
},
waitForEntityPosition: function RealizingAnimatingState_waitForEntityPosition(entity) {
return this.view._getLayoutCompleted();
},
updateTree: function RealizingAnimatingState_updateTree(count, delta, modifiedElements) {
if (this.animating) {
var previousModifiedElements = this.modifiedElements;
this.count = count;
this.delta = delta;
this.modifiedElements = modifiedElements;
return previousModifiedElements ? WinJS.Promise.cancel : this.animatePromise;
} else {
return this.view._updateTreeImpl(count, delta, modifiedElements);
}
},
setLoadingState: function RealizingAnimatingState_setLoadingState(state) {
this.view._listView._setViewState(state);
}
});
/*
The view enters this state when the tree is built, layout and realized after animations have
finished. The layout can still laying out items outside of realized view during this stage.
CompletedState => RealizingState | ScrollingState | LayingoutState | BuildingState | LayingoutNewContainersState
*/
var CompletedState = WinJS.Class.derive(CanceledState, function CompletedState_ctor(view) {
this.view = view;
}, {
name: 'CompletedState',
enter: function CompletedState_enter() {
this._stopped = false;
this.view._setupDeferredActions();
this.view._realizationLevel = WinJS.UI._VirtualizeContentsView._realizationLevel.normal;
this.view._listView._raiseViewComplete();
// _raiseViewComplete will cause user event listener code to run synchronously.
// If any updates are made to the Listview, this state will be stopped by the updater.
// We don't want to change state to LayingoutNewContainersState if that happens.
if (this.view._state === this && this.view._relayoutInComplete && !this._stopped) {
this.view._setState(LayingoutNewContainersState);
}
},
stop: function CompletedState_stop() {
this._stopped = true;
// Call base class method.
CanceledState.prototype.stop.call(this);
},
layoutNewContainers: function CompletedState_layoutNewContainers() {
this.view._createLayoutSignal();
this.view._setState(LayingoutNewContainersState);
},
updateTree: function CompletedState_updateTree(count, delta, modifiedElements) {
return this.view._updateTreeImpl(count, delta, modifiedElements, true);
}
});
/*
The view waits in this state for previous layout pass to finish.
LayingoutNewContainersState => RealizingState | ScrollingState | LayingoutState | BuildingState
*/
var LayingoutNewContainersState = WinJS.Class.derive(CanceledState, function LayingoutNewContainersState(view) {
this.view = view;
}, {
name: 'LayingoutNewContainersState',
enter: function LayingoutNewContainersState_enter() {
var that = this;
// _layoutWork is completed when the previous layout pass is done. _getLayoutCompleted will be completed when these new containers are laid out
this.promise = WinJS.Promise.join([this.view.deferTimeout, this.view._layoutWork]);
this.promise.then(function () {
that.view._relayoutInComplete = false;
that.relayout(CanceledState);
});
},
stop: function LayingoutNewContainersState_stop() {
// cancelRealize cancels ariaSetup which can still be in progress
this.promise.cancel();
this.view._cancelRealize();
},
realizePage: function LayingoutNewContainersState_realizePage(NewStateType) {
// in this state realizePage needs to run layout before realizing items
this.stop();
this.view._setState(LayingoutState, NewStateType);
},
layoutNewContainers: function LayingoutNewContainersState_layoutNewContainers() {
this.view._createLayoutSignal();
}
});
return _VirtualizeContentsView;
})
});
})(this, WinJS);
(function datePickerInit(WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Allows users to pick a date value.
///
///
/// Date Picker
///
///
/// ]]>
/// Occurs when the current date changes.
///
///
///
DatePicker: WinJS.Namespace._lazy(function () {
// Constants definition
var DEFAULT_DAY_PATTERN = 'day',
DEFAULT_MONTH_PATTERN = '{month.full}',
DEFAULT_YEAR_PATTERN = 'year.full';
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/datePicker").value; },
get selectDay() { return WinJS.Resources._getWinJSString("ui/selectDay").value; },
get selectMonth() { return WinJS.Resources._getWinJSString("ui/selectMonth").value; },
get selectYear() { return WinJS.Resources._getWinJSString("ui/selectYear").value; },
};
var yearFormatCache = {};
function newFormatter(pattern, calendar, defaultPattern) {
var dtf = Windows.Globalization.DateTimeFormatting;
pattern = !pattern ? defaultPattern : pattern;
var c = new dtf.DateTimeFormatter(pattern);
if (calendar) {
return new dtf.DateTimeFormatter(pattern, c.languages, c.geographicRegion, calendar, c.clock);
}
return c;
};
function formatCacheLookup(pattern, calendar, defaultPattern) {
var pat = yearFormatCache[pattern];
if (!pat) {
pat = yearFormatCache[pattern] = {};
}
var cal = pat[calendar];
if (!cal) {
cal = pat[calendar] = {};
}
var def = cal[defaultPattern];
if (!def) {
def = cal[defaultPattern] = {};
def.formatter = newFormatter(pattern, calendar, defaultPattern);
def.years = {};
}
return def;
}
function formatYear(pattern, calendar, defaultPattern, datePatterns, order, cal) {
var cache = formatCacheLookup(pattern, calendar, defaultPattern);
var y = cache.years[cal.year + "-" + cal.era];
if (!y) {
y = cache.formatter.format(cal.getDateTime());
cache.years[cal.year + "-" + cal.era] = y;
}
return y;
}
function formatMonth(pattern, calendar, defaultPattern, cal) {
var cache = formatCacheLookup(pattern, calendar, defaultPattern);
// can't cache actual month names because the hebrew calendar varies
// the month name depending on religious holidays and leap months.
//
return cache.formatter.format(cal.getDateTime());
}
function formatDay(pattern, calendar, defaultPattern, cal) {
var cache = formatCacheLookup(pattern, calendar, defaultPattern);
// can't cache actual day names because the format may include the day of the week,
// which, of course, varies from month to month.
//
return cache.formatter.format(cal.getDateTime());
}
function newCal(calendar) {
var glob = Windows.Globalization;
var c = new glob.Calendar();
if (calendar) {
return new glob.Calendar(c.languages, calendar, c.getClock());
}
return c;
};
function yearDiff(start, end) {
var yearCount = 0;
if (start.era == end.era) {
yearCount = end.year - start.year;
}
else {
while (start.era !== end.era || start.year !== end.year) {
yearCount++;
start.addYears(1);
}
}
return yearCount;
}
var DatePicker = WinJS.Class.define(function DatePicker_ctor(element, options) {
///
/// Creates a new DatePicker control.
///
/// The DOM element that will host the DatePicker control.
///
///
/// An object that contains one or more property/value pairs to apply to the new control. Each property of the options object corresponds
/// to one of the control's properties or events.
///
/// A constructed DatePicker control.
///
///
// Default to current date
this._currentDate = new Date();
// Default to +/- 100 years
this._minYear = this._currentDate.getFullYear() - 100;
this._maxYear = this._currentDate.getFullYear() + 100;
this._datePatterns = {
date: null,
month: null,
year: null
};
element = element || document.createElement("div");
WinJS.Utilities.addClass(element, "win-disposable");
element.winControl = this;
var label = element.getAttribute("aria-label");
if (!label) {
element.setAttribute("aria-label", strings.ariaLabel);
}
// Options should be set after the element is initialized which is
// the same order of operation as imperatively setting options.
this._init(element);
WinJS.UI.setOptions(this, options);
}, {
_information: null,
_currentDate: null,
_calendar: null,
_disabled: false,
_dateElement: null,
_dateControl: null,
_monthElement: null,
_monthControl: null,
_minYear: null,
_maxYear: null,
_yearElement: null,
_yearControl: null,
_datePatterns: {
date: null,
month: null,
year: null
},
_addAccessibilityAttributes: function () {
//see http://www.w3.org/TR/wai-aria/rdf_model.png for details
this._domElement.setAttribute("role", "group");
this._dateElement.setAttribute("aria-label", strings.selectDay);
this._monthElement.setAttribute("aria-label", strings.selectMonth);
this._yearElement.setAttribute("aria-label", strings.selectYear);
},
_addControlsInOrder: function () {
var e = this._domElement;
var u = WinJS.Utilities;
var that = this;
var orderIndex = 0; // don't use forEach's index, because "era" is in the list
that._information.order.forEach(function (s) {
switch (s) {
case "month":
e.appendChild(that._monthElement);
u.addClass(that._monthElement, "win-order" + (orderIndex++));
break;
case "date":
e.appendChild(that._dateElement);
u.addClass(that._dateElement, "win-order" + (orderIndex++));
break;
case "year":
e.appendChild(that._yearElement);
u.addClass(that._yearElement, "win-order" + (orderIndex++));
break;
}
});
},
_createControlElements: function () {
this._monthElement = document.createElement("select");
this._monthElement.className = "win-datepicker-month";
this._dateElement = document.createElement("select");
this._dateElement.className = "win-datepicker-date";
this._yearElement = document.createElement("select");
this._yearElement.className = "win-datepicker-year";
},
_createControls: function () {
var that = this;
var info = this._information;
var index = info.getIndex(this.current);
if (info.forceLanguage) {
this._domElement.setAttribute("lang", info.forceLanguage);
this._domElement.setAttribute("dir", info.isRTL ? "rtl" : "ltr");
}
this._yearControl = new WinJS.UI._Select(this._yearElement, {
dataSource: this._information.years,
disabled: this.disabled,
index: index.year
});
this._monthControl = new WinJS.UI._Select(this._monthElement, {
dataSource: this._information.months(index.year),
disabled: this.disabled,
index: index.month
});
this._dateControl = new WinJS.UI._Select(this._dateElement, {
dataSource: this._information.dates(index.year, index.month),
disabled: this.disabled,
index: index.date
});
this._wireupEvents();
},
dispose: function () {
///
///
/// Disposes this control.
///
///
///
},
///
/// Gets or sets the calendar to use.
///
///
calendar: {
get: function () {
return this._calendar;
},
set: function (value) {
this._calendar = value;
this._setElement(this._domElement);
}
},
///
/// Gets or sets the current date of the DatePicker.
///
///
current: {
get: function () {
var d = this._currentDate;
var y = d.getFullYear();
return new Date(Math.max(Math.min(this.maxYear, y), this.minYear), d.getMonth(), d.getDate(), 12, 0, 0, 0);
},
set: function (value) {
var newDate;
if (typeof (value) === "string") {
newDate = new Date(Date.parse(value));
newDate.setHours(12, 0, 0, 0);
}
else {
newDate = value;
}
var oldDate = this._currentDate;
if (oldDate != newDate) {
this._currentDate = newDate;
this._updateDisplay();
}
}
},
///
/// Gets or sets a value that specifies whether the DatePicker is disabled. A value of true indicates that the DatePicker is disabled.
///
///
disabled: {
get: function () { return this._disabled; },
set: function (value) {
if (this._disabled !== value) {
this._disabled = value;
// all controls get populated at the same time, so any check is OK
//
if (this._yearControl) {
this._monthControl.setDisabled(value);
this._dateControl.setDisabled(value);
this._yearControl.setDisabled(value);
}
}
}
},
///
/// Gets or sets the display pattern for the date.
///
///
datePattern: {
get: function () { return this._datePatterns.date; },
set: function (value) {
if (this._datePatterns.date !== value) {
this._datePatterns.date = value;
this._init();
}
}
},
///
/// Gets the DOM element for the DatePicker.
///
///
element: {
get: function () { return this._domElement; }
},
_setElement: function (element) {
this._domElement = this._domElement || element;
if (!this._domElement) { return; }
WinJS.Utilities.empty(this._domElement);
WinJS.Utilities.addClass(this._domElement, "win-datepicker");
this._updateInformation();
this._createControlElements();
this._addControlsInOrder();
this._createControls();
this._addAccessibilityAttributes();
},
///
/// Gets or sets the minimum Gregorian year available for picking.
///
///
minYear: {
get: function () {
return this._information.getDate({ year: 0, month: 0, date: 0 }).getFullYear();
},
set: function (value) {
if (this._minYear !== value) {
this._minYear = value;
if (value > this._maxYear) {
this._maxYear = value;
}
this._updateInformation();
if (this._yearControl) {
this._yearControl.dataSource = this._information.years;
}
this._updateDisplay();
}
}
},
///
/// Gets or sets the maximum Gregorian year available for picking.
///
///
maxYear: {
get: function () {
var index = {
year: this._information.years.getLength() - 1
};
index.month = this._information.months(index.year).getLength() - 1;
index.date = this._information.dates(index.year, index.month).getLength() - 1;
return this._information.getDate(index).getFullYear();
},
set: function (value) {
if (this._maxYear !== value) {
this._maxYear = value;
if (value < this._minYear) {
this._minYear = value;
}
this._updateInformation();
if (this._yearControl) {
this._yearControl.dataSource = this._information.years;
}
this._updateDisplay();
}
}
},
///
/// Gets or sets the display pattern for the month.
///
///
monthPattern: {
get: function () { return this._datePatterns.month; },
set: function (value) {
if (this._datePatterns.month !== value) {
this._datePatterns.month = value;
this._init();
}
}
},
_updateInformation: function () {
// since "year" in the date ctor can be two digit (85 == 1985), we need
// to force "full year" to capture dates < 100 a.d.
//
var min = new Date(this._minYear, 0, 1, 12, 0, 0);
var max = new Date(this._maxYear, 11, 31, 12, 0, 0);
min.setFullYear(this._minYear);
max.setFullYear(this._maxYear);
this._information = WinJS.UI.DatePicker.getInformation(min, max, this._calendar, this._datePatterns);
},
_init: function (element) {
this._setElement(element);
},
_updateDisplay: function () {
if (!this._domElement)
return;
// all controls get populated at the same time, so any check is OK
//
if (this._yearControl) {
//Render display index based on constraints (minYear and maxYear constraints)
//Will not modify current date
var index = this._information.getIndex(this.current);
this._yearControl.index = index.year;
this._monthControl.dataSource = this._information.months(index.year);
this._monthControl.index = index.month;
this._dateControl.dataSource = this._information.dates(index.year, index.month);
this._dateControl.index = index.date;
}
},
_wireupEvents: function () {
var that = this;
function changed() {
that._currentDate = that._information.getDate({ year: that._yearControl.index, month: that._monthControl.index, date: that._dateControl.index }, that._currentDate);
var index = that._information.getIndex(that._currentDate);
// Changing the month (or year, if the current date is 2/29) changes the day range, and could have made the day selection invalid
that._monthControl.dataSource = that._information.months(index.year)
that._monthControl.index = index.month;
that._dateControl.dataSource = that._information.dates(index.year, index.month);
that._dateControl.index = index.date;
}
this._dateElement.addEventListener("change", changed, false);
this._monthElement.addEventListener("change", changed, false);
this._yearElement.addEventListener("change", changed, false);
},
///
/// Gets or sets the display pattern for year.
///
///
yearPattern: {
get: function () { return this._datePatterns.year; },
set: function (value) {
if (this._datePatterns.year !== value) {
this._datePatterns.year = value;
this._init();
}
}
},
}, {
_getInformationWinRT: function (startDate, endDate, calendar, datePatterns) {
datePatterns = datePatterns || { date: DEFAULT_DAY_PATTERN, month: DEFAULT_MONTH_PATTERN, year: DEFAULT_YEAR_PATTERN };
var tempCal = newCal(calendar);
var monthCal = newCal(calendar);
var dayCal = newCal(calendar);
tempCal.setToMin();
var minDateTime = tempCal.getDateTime();
tempCal.setToMax();
var maxDateTime = tempCal.getDateTime();
function clamp(date) {
return new Date(Math.min(new Date(Math.max(minDateTime, date)), maxDateTime));;
}
tempCal.hour = 12;
startDate = clamp(startDate);
endDate = clamp(endDate);
tempCal.setDateTime(endDate);
var end = { year: tempCal.year, era: tempCal.era };
tempCal.setDateTime(startDate);
var minYear = tempCal.year;
var yearLen = 0;
yearLen = yearDiff(tempCal, end) + 1;
// Explicity use a template that's equivalent to a longdate template
// as longdate/shortdate can be overriden by the user
var dateformat = formatCacheLookup("day month.full year", calendar).formatter;
var localdatepattern = dateformat.patterns[0];
var isRTL = localdatepattern.charCodeAt(0) === 8207;
var order = ["date", "month", "year"];
var indexes = {
month: localdatepattern.indexOf("{month"),
date: localdatepattern.indexOf("{day"),
year: localdatepattern.indexOf("{year")
};
order.sort(function (a, b) {
if (indexes[a] < indexes[b]) { return -1; }
else if (indexes[a] > indexes[b]) { return 1; }
else { return 0; }
});
var yearSource = (function () {
return {
getLength: function () { return yearLen; },
getValue: function (index) {
tempCal.setDateTime(startDate);
tempCal.addYears(index);
return formatYear(datePatterns.year, calendar, DEFAULT_YEAR_PATTERN, datePatterns, order, tempCal);
}
}
})();
var monthSource = function (yearIndex) {
monthCal.setDateTime(startDate);
monthCal.addYears(yearIndex);
return {
getLength: function () { return monthCal.numberOfMonthsInThisYear; },
getValue: function (index) {
monthCal.month = monthCal.firstMonthInThisYear;
monthCal.addMonths(index);
return formatMonth(datePatterns.month, calendar, DEFAULT_MONTH_PATTERN, monthCal);
}
};
};
var dateSource = function (yearIndex, monthIndex) {
dayCal.setDateTime(startDate);
dayCal.addYears(yearIndex);
dayCal.month = dayCal.firstMonthInThisYear;
dayCal.addMonths(monthIndex);
dayCal.day = dayCal.firstDayInThisMonth;
return {
getLength: function () { return dayCal.numberOfDaysInThisMonth; },
getValue: function (index) {
dayCal.day = dayCal.firstDayInThisMonth;
dayCal.addDays(index);
return formatDay(datePatterns.date, calendar, DEFAULT_DAY_PATTERN, dayCal);
}
};
};
return {
isRTL: isRTL,
forceLanguage: dateformat.resolvedLanguage,
order: order,
getDate: function (index, lastDate) {
var lastCal;
if (lastDate) {
tempCal.setDateTime(lastDate);
lastCal = { year: tempCal.year, month: tempCal.month, day: tempCal.day };
}
var c = tempCal;
c.setDateTime(startDate);
c.addYears(index.year);
var guessMonth;
if (c.firstMonthInThisYear > c.lastMonthInThisYear) {
if (index.month + c.firstMonthInThisYear > c.numberOfMonthsInThisYear) {
guessMonth = index.month + c.firstMonthInThisYear - c.numberOfMonthsInThisYear;
} else {
guessMonth = index.month + c.firstMonthInThisYear;
}
if (lastCal && lastCal.year !== c.year) {
// Year has changed in some transitions in Thai Calendar, this will change the first month, and last month indices of the year.
guessMonth = Math.max(Math.min(lastCal.month, c.numberOfMonthsInThisYear), 1);
}
} else {
if (lastCal && lastCal.year !== c.year) {
// Year has changed in some transitions in Thai Calendar, this will change the first month, and last month indices of the year.
guessMonth = Math.max(Math.min(lastCal.month, c.firstMonthInThisYear + c.numberOfMonthsInThisYear - 1), c.firstMonthInThisYear);
} else {
guessMonth = Math.max(Math.min(index.month + c.firstMonthInThisYear, c.firstMonthInThisYear + c.numberOfMonthsInThisYear - 1), c.firstMonthInThisYear);
}
}
c.month = guessMonth;
var guessDay = Math.max(Math.min(index.date + c.firstDayInThisMonth, c.firstDayInThisMonth + c.numberOfDaysInThisMonth - 1), c.firstDayInThisMonth);
if (lastCal && (lastCal.year !== c.year || lastCal.month !== c.month)) {
guessDay = Math.max(Math.min(lastCal.day, c.firstDayInThisMonth + c.numberOfDaysInThisMonth - 1), c.firstDayInThisMonth);
}
c.day = c.firstDayInThisMonth;
c.addDays(guessDay - c.firstDayInThisMonth);
return c.getDateTime();
},
getIndex: function (date) {
var curDate = clamp(date);
tempCal.setDateTime(curDate);
var cur = { year: tempCal.year, era: tempCal.era };
var yearIndex = 0;
tempCal.setDateTime(startDate);
tempCal.month = 1;
yearIndex = yearDiff(tempCal, cur);
tempCal.setDateTime(curDate);
var monthIndex = tempCal.month - tempCal.firstMonthInThisYear;
if (monthIndex < 0) {
// A special case is in some ThaiCalendar years first month
// of the year is April, last month is March and month flow is wrap-around
// style; April, March .... November, December, January, February, March. So the first index
// will be 4 and last index will be 3. We are handling the case to convert this wraparound behavior
// into selected index.
monthIndex = tempCal.month - tempCal.firstMonthInThisYear + tempCal.numberOfMonthsInThisYear;
}
var dateIndex = tempCal.day - tempCal.firstDayInThisMonth;
var index = {
year: yearIndex,
month: monthIndex,
date: dateIndex
};
return index;
},
years: yearSource,
months: monthSource,
dates: dateSource
};
},
_getInformationJS: function (startDate, endDate) {
var minYear = startDate.getFullYear();
var maxYear = endDate.getFullYear();
var yearSource = {
getLength: function () { return Math.max(0, maxYear - minYear + 1); },
getValue: function (index) { return minYear + index; }
};
var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
var monthSource = function (yearIndex) {
return {
getLength: function () { return months.length; },
getValue: function (index) { return months[index]; },
getMonthNumber: function (index) { return Math.min(index, months.length - 1); }
};
};
var dateSource = function (yearIndex, monthIndex) {
var temp = new Date();
var year = yearSource.getValue(yearIndex);
// The +1 is needed to make using a day of 0 work correctly
var month = monthIndex + 1; // index is always correct, unlike getMonth which changes when the date is invalid
temp.setFullYear(year, month, 0);
var maxValue = temp.getDate();
return {
getLength: function () { return maxValue; },
getValue: function (index) { return "" + (index + 1); },
getDateNumber: function (index) { return Math.min(index + 1, maxValue); }
};
};
return {
order: ["month", "date", "year"],
getDate: function (index) {
return new Date(
yearSource.getValue(index.year),
monthSource(index.year).getMonthNumber(index.month),
dateSource(index.year, index.month).getDateNumber(index.date),
12, 0
);
},
getIndex: function (date) {
var yearIndex = 0;
var year = date.getFullYear();
if (year < minYear) {
yearIndex = 0;
}
else if (year > this.maxYear) {
yearIndex = yearSource.getLength() - 1;
}
else {
yearIndex = date.getFullYear() - minYear;
}
var monthIndex = Math.min(date.getMonth(), monthSource(yearIndex).getLength());
var dateIndex = Math.min(date.getDate() - 1, dateSource(yearIndex, monthIndex).getLength());
return {
year: yearIndex,
month: monthIndex,
date: dateIndex
};
},
years: yearSource,
months: monthSource,
dates: dateSource
};
}
});
if (WinJS.Utilities.hasWinRT) {
DatePicker.getInformation = DatePicker._getInformationWinRT;
}
else {
DatePicker.getInformation = DatePicker._getInformationJS;
}
WinJS.Class.mix(DatePicker, WinJS.Utilities.createEventProperties("change"));
WinJS.Class.mix(DatePicker, WinJS.UI.DOMEventMixin);
return DatePicker;
})
});
})(WinJS);
(function timePickerInit(WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Allows users to select time values.
///
///
/// Time Picker
///
///
/// ]]>
/// Occurs when the time changes.
///
///
///
TimePicker: WinJS.Namespace._lazy(function () {
// Constants definition
var DEFAULT_MINUTE_PATTERN = "{minute.integer(2)}",
DEFAULT_HOUR_PATTERN = "{hour.integer(1)}",
DEFAULT_PERIOD_PATTERN = "{period.abbreviated(2)}";
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/timePicker").value; },
get selectHour() { return WinJS.Resources._getWinJSString("ui/selectHour").value; },
get selectMinute() { return WinJS.Resources._getWinJSString("ui/selectMinute").value; },
get selectAMPM() { return WinJS.Resources._getWinJSString("ui/selectAMPM").value; },
};
// date1 and date2 must be Date objects with their date portions set to the
// sentinel date.
var areTimesEqual = function (date1, date2) {
return date1.getHours() === date2.getHours() &&
date1.getMinutes() === date2.getMinutes();
};
var TimePicker = WinJS.Class.define(function TimePicker_ctor(element, options) {
///
/// Initializes a new instance of the TimePicker control
///
/// The DOM element associated with the TimePicker control.
///
///
/// The set of options to be applied initially to the TimePicker control.
///
/// A constructed TimePicker control.
///
///
// Default to current time
this._currentTime = WinJS.UI.TimePicker._sentinelDate();
element = element || document.createElement("div");
WinJS.Utilities.addClass(element, "win-disposable");
element.winControl = this;
var label = element.getAttribute("aria-label");
if (!label) {
element.setAttribute("aria-label", strings.ariaLabel);
}
this._timePatterns = {
minute: null,
hour: null,
period: null
};
// Options should be set after the element is initialized which is
// the same order of operation as imperatively setting options.
this._init(element);
WinJS.UI.setOptions(this, options);
}, {
_currentTime: null,
_clock: null,
_disabled: false,
_hourElement: null,
_hourControl: null,
_minuteElement: null,
_minuteControl: null,
_ampmElement: null,
_ampmControl: null,
_minuteIncrement: 1,
_timePatterns: {
minute: null,
hour: null,
period: null
},
_information: null,
_addAccessibilityAttributes: function () {
//see http://www.w3.org/TR/wai-aria/rdf_model.png for details
this._domElement.setAttribute("role", "group");
this._hourElement.setAttribute("aria-label", strings.selectHour);
this._minuteElement.setAttribute("aria-label", strings.selectMinute);
if (this._ampmElement) {
this._ampmElement.setAttribute("aria-label", strings.selectAMPM);
}
},
_addControlsInOrder: function (info) {
var that = this;
var u = WinJS.Utilities;
info.order.forEach(function (s, index) {
switch (s) {
case "hour":
that._domElement.appendChild(that._hourElement);
u.addClass(that._hourElement, "win-order" + index);
break;
case "minute":
that._domElement.appendChild(that._minuteElement);
u.addClass(that._minuteElement, "win-order" + index);
break;
case "period":
if (that._ampmElement) {
that._domElement.appendChild(that._ampmElement);
u.addClass(that._ampmElement, "win-order" + index);
}
break;
}
});
},
dispose: function () {
///
///
/// Disposes this TimePicker.
///
///
///
},
///
/// Gets or sets the type of clock to display (12HourClock or 24HourClock). It defaults to the user setting.
///
///
clock: {
get: function () {
return this._clock;
},
set: function (value) {
if (this._clock !== value) {
this._clock = value;
this._init();
}
}
},
///
/// Gets or sets the current date (and time) of the TimePicker.
///
///
current: {
get: function () {
var cur = this._currentTime;
if (cur) {
var time = WinJS.UI.TimePicker._sentinelDate();
time.setHours(cur.getHours()); // accounts for AM/PM
time.setMinutes(this._getMinutesIndex(cur) * this.minuteIncrement);
time.setSeconds(0);
time.setMilliseconds(0);
return time;
}
else {
return cur;
}
},
set: function (value) {
var that = this;
var newTime;
if (typeof (value) === "string") {
newTime = WinJS.UI.TimePicker._sentinelDate();
newTime.setTime(Date.parse(newTime.toDateString() + " " + value));
}
else {
newTime = WinJS.UI.TimePicker._sentinelDate();
newTime.setHours(value.getHours());
newTime.setMinutes(value.getMinutes());
}
var oldTime = this._currentTime;
if (!areTimesEqual(oldTime, newTime)) {
this._currentTime = newTime;
this._updateDisplay();
}
}
},
///
/// Specifies whether the TimePicker is disabled.
///
///
disabled: {
get: function () { return this._disabled; },
set: function (value) {
if (this._disabled !== value) {
this._disabled = value;
if (this._hourControl) {
this._hourControl.setDisabled(value);
this._minuteControl.setDisabled(value);
}
if (this._ampmControl) {
this._ampmControl.setDisabled(value);
}
}
}
},
///
/// Gets the DOM element for the TimePicker.
///
///
element: {
get: function () { return this._domElement; }
},
_init: function (element) {
this._setElement(element);
this._updateDisplay();
},
///
/// Gets or sets the display pattern for the hour.
///
///
hourPattern: {
get: function () { return this._timePatterns.hour.pattern; },
set: function (value) {
if (this._timePatterns.hour !== value) {
this._timePatterns.hour = value;
this._init();
}
}
},
_getHoursAmpm: function (time) {
var hours24 = time.getHours();
if (this._ampmElement) {
if (hours24 === 0) {
return { hours: 12, ampm: 0 };
}
else if (hours24 < 12) {
return { hours: hours24, ampm: 0 };
}
return { hours: hours24 - 12, ampm: 1 };
}
return { hours: hours24 };
},
_getHoursIndex: function (hours) {
if (this._ampmElement && hours === 12) {
return 0;
}
return hours;
},
_getMinutesIndex: function (time) {
return parseInt(time.getMinutes() / this.minuteIncrement);
},
///
/// Gets or sets the minute increment. For example, "15" specifies that the TimePicker minute control should display only the choices 00, 15, 30, 45.
///
///
minuteIncrement: {
//prevent divide by 0, and leave user's input intact
get: function () { return Math.max(1, Math.abs(this._minuteIncrement | 0) % 60); },
set: function (value) {
if (this._minuteIncrement != value) {
this._minuteIncrement = value;
this._init();
}
}
},
///
/// Gets or sets the display pattern for the minute.
///
///
minutePattern: {
get: function () { return this._timePatterns.minute.pattern; },
set: function (value) {
if (this._timePatterns.minute !== value) {
this._timePatterns.minute = value;
this._init();
}
}
},
///
/// Gets or sets the display pattern for the period.
///
///
periodPattern: {
get: function () { return this._timePatterns.period.pattern; },
set: function (value) {
if (this._timePatterns.period !== value) {
this._timePatterns.period = value;
this._init();
}
}
},
_setElement: function (element) {
this._domElement = this._domElement || element;
if (!this._domElement) { return; }
var info = WinJS.UI.TimePicker.getInformation(this.clock, this.minuteIncrement, this._timePatterns);
this._information = info;
if (info.forceLanguage) {
this._domElement.setAttribute("lang", info.forceLanguage);
this._domElement.setAttribute("dir", info.isRTL ? "rtl" : "ltr");
}
WinJS.Utilities.empty(this._domElement);
WinJS.Utilities.addClass(this._domElement, "win-timepicker");
this._hourElement = document.createElement("select");
WinJS.Utilities.addClass(this._hourElement, "win-timepicker-hour");
this._minuteElement = document.createElement("select");
WinJS.Utilities.addClass(this._minuteElement, "win-timepicker-minute");
this._ampmElement = null;
if (info.clock === "12HourClock") {
this._ampmElement = document.createElement("select");
WinJS.Utilities.addClass(this._ampmElement, "win-timepicker-period");
}
this._addControlsInOrder(info);
var that = this;
var hoursAmpm = this._getHoursAmpm(this.current);
this._hourControl = new WinJS.UI._Select(this._hourElement, {
dataSource: this._getInfoHours(),
disabled: this.disabled,
index: this._getHoursIndex(hoursAmpm.hours)
});
this._minuteControl = new WinJS.UI._Select(this._minuteElement, {
dataSource: info.minutes,
disabled: this.disabled,
index: this._getMinutesIndex(this.current)
});
this._ampmControl = null;
if (this._ampmElement) {
this._ampmControl = new WinJS.UI._Select(this._ampmElement, {
dataSource: info.periods,
disabled: this.disabled,
index: hoursAmpm.ampm
});
}
this._wireupEvents();
this._updateValues();
this._addAccessibilityAttributes();
},
_getInfoHours: function () {
return this._information.hours;
},
_updateLayout: function () {
if (!this._domElement)
return;
this._updateValues();
},
_updateValues: function () {
if (this._hourControl) {
var hoursAmpm = this._getHoursAmpm(this.current);
if (this._ampmControl) {
this._ampmControl.index = hoursAmpm.ampm;
}
this._hourControl.index = this._getHoursIndex(hoursAmpm.hours);
this._minuteControl.index = this._getMinutesIndex(this.current);
}
},
_updateDisplay: function () {
//Render display index based on constraints (minuteIncrement)
//Will not modify current time
var hoursAmpm = this._getHoursAmpm(this.current);
if (this._ampmControl) {
this._ampmControl.index = hoursAmpm.ampm;
}
if (this._hourControl) {
this._hourControl.index = this._getHoursIndex(hoursAmpm.hours);
this._minuteControl.index = this._getMinutesIndex(this.current);
}
},
_wireupEvents: function () {
var that = this;
var fixupHour = function () {
var hour = that._hourControl.index;
if (that._ampmElement) {
if (that._ampmControl.index === 1) {
if (hour !== 12) {
hour += 12;
}
}
}
return hour;
};
var changed = function () {
var hour = fixupHour();
that._currentTime.setHours(hour);
that._currentTime.setMinutes(that._minuteControl.index * that.minuteIncrement);
};
this._hourElement.addEventListener("change", changed, false);
this._minuteElement.addEventListener("change", changed, false);
if (this._ampmElement) {
this._ampmElement.addEventListener("change", changed, false);
}
}
}, {
_sentinelDate: function () {
// This is July 15th, 2011 as our sentinel date. There are no known
// daylight savings transitions that happened on that date.
var current = new Date();
return new Date(2011, 6, 15, current.getHours(), current.getMinutes());
},
_getInformationWinRT: function (clock, minuteIncrement, timePatterns) {
var newFormatter = function (pattern, defaultPattern) {
var dtf = Windows.Globalization.DateTimeFormatting;
pattern = !pattern ? defaultPattern : pattern;
var formatter = new dtf.DateTimeFormatter(pattern);
if (clock) {
formatter = dtf.DateTimeFormatter(pattern, formatter.languages, formatter.geographicRegion, formatter.calendar, clock);
}
return formatter;
}
var glob = Windows.Globalization;
var calendar = new glob.Calendar();
if (clock) {
calendar = new glob.Calendar(calendar.languages, calendar.getCalendarSystem(), clock);
}
calendar.setDateTime(WinJS.UI.TimePicker._sentinelDate());
var computedClock = calendar.getClock();
var numberOfHours = 24;
numberOfHours = calendar.numberOfHoursInThisPeriod;
var periods = (function () {
var periodFormatter = newFormatter(timePatterns.period, DEFAULT_PERIOD_PATTERN);
return {
getLength: function () { return 2; },
getValue: function (index) {
var date = WinJS.UI.TimePicker._sentinelDate();
if (index == 0) {
date.setHours(1);
var am = periodFormatter.format(date);
return am;
}
if (index == 1) {
date.setHours(13);
var pm = periodFormatter.format(date);
return pm;
}
return null;
}
};
})();
// Determine minute format from the DateTimeFormatter
var minutes = (function (index) {
var minuteFormatter = newFormatter(timePatterns.minute, DEFAULT_MINUTE_PATTERN);
var now = WinJS.UI.TimePicker._sentinelDate();
return {
getLength: function () { return 60 / minuteIncrement; },
getValue: function (index) {
var display = index * minuteIncrement;
now.setMinutes(display);
return minuteFormatter.format(now);
}
};
})();
// Determine hour format from the DateTimeFormatter
var hours = (function (index) {
var hourFormatter = newFormatter(timePatterns.hour, DEFAULT_HOUR_PATTERN);
var now = WinJS.UI.TimePicker._sentinelDate();
return {
getLength: function () { return numberOfHours },
getValue: function (index) {
now.setHours(index);
return hourFormatter.format(now);
}
}
})();
// Determine the order of the items from the DateTimeFormatter.
// "hour minute" also returns the period (if needed).
//
var hourMinuteFormatter = newFormatter("hour minute");
var pattern = hourMinuteFormatter.patterns[0];
var order = ["hour", "minute"];
var indexes = {
period: pattern.indexOf("{period"),
hour: pattern.indexOf("{hour"),
minute: pattern.indexOf("{minute")
};
if (indexes.period > -1) {
order.push("period");
}
var DateTimeFormatter = Windows.Globalization.DateTimeFormatting.DateTimeFormatter;
var dtf = new DateTimeFormatter("month.full", Windows.Globalization.ApplicationLanguages.languages, "ZZ", "GregorianCalendar", "24HourClock");
var pat = dtf.patterns[0];
var isRTL = pat.charCodeAt(0) === 8207;
if (isRTL) {
var temp = indexes.hour;
indexes.hour = indexes.minute;
indexes.minute = temp;
}
order.sort(function (a, b) {
if (indexes[a] < indexes[b]) { return -1; }
else if (indexes[a] > indexes[b]) { return 1; }
else { return 0; }
});
return { minutes: minutes, hours: hours, clock: computedClock, periods: periods, order: order, forceLanguage: hourMinuteFormatter.resolvedLanguage, isRTL: isRTL };
},
_getInformationJS: function (clock, minuteIncrement) {
var hours = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
var minutes = new Object();
minutes.getLength = function () { return 60 / minuteIncrement; }
minutes.getValue = function (index) {
var display = index * minuteIncrement;
if (display < 10) {
return "0" + display.toString();
}
else {
return display.toString();
}
};
var order = ["hour", "minute", "period"];
if (clock === "24HourClock") {
hours = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23];
order = ["hour", "minute"];
}
return { minutes: minutes, hours: hours, clock: clock || "12HourClock", periods: ["AM", "PM"], order: order };
}
});
if (WinJS.Utilities.hasWinRT) {
TimePicker.getInformation = TimePicker._getInformationWinRT;
}
else {
TimePicker.getInformation = TimePicker._getInformationJS;
}
WinJS.Class.mix(TimePicker, WinJS.Utilities.createEventProperties("change"));
WinJS.Class.mix(TimePicker, WinJS.UI.DOMEventMixin);
return TimePicker;
})
});
})(WinJS);
(function selectInit(WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
_Select: WinJS.Namespace._lazy(function () {
var encodeHtmlRegEx = /[&<>'"]/g;
var encodeHtmlEscapeMap = {
"&": "&",
"<": "<",
">": ">",
"'": "'",
'"': """
};
var stringDirectionRegEx = /[\u200e\u200f]/g;
function encodeHtml(str) {
return str.replace(encodeHtmlRegEx, function (m) {
return encodeHtmlEscapeMap[m] || "";
});
};
function stripDirectionMarker(str) {
return str.replace(stringDirectionRegEx, "");
}
function stockGetValue(index) { return this[index]; }
function stockGetLength() { return this.length; }
function fixDataSource(dataSource) {
if (!dataSource.getValue) {
dataSource.getValue = stockGetValue
}
if (!dataSource.getLength) {
dataSource.getLength = stockGetLength
}
return dataSource;
}
return WinJS.Class.define(function _Select_ctor(element, options) {
// This is an implementation detail of the TimePicker and DatePicker, designed
// to provide a primitive "data bound" select control. This is not designed to
// be used outside of the TimePicker and DatePicker controls.
//
this._dataSource = fixDataSource(options.dataSource);
this._index = options.index || 0;
this._domElement = element;
// Mark this as a tab stop
this._domElement.tabIndex = 0;
if (options.disabled) {
this.setDisabled(options.disabled);
}
var that = this;
this._domElement.addEventListener("change", function (e) {
//Should be set to _index to prevent events from firing twice
that._index = that._domElement.selectedIndex;
}, false);
//update runtime accessibility value after initialization
this._createSelectElement();
}, {
_index: 0,
_dataSource: null,
dataSource: {
get: function () { return this._dataSource; },
set: function (value) {
this._dataSource = fixDataSource(value);
//Update layout as data source change
if (this._domElement) {
this._createSelectElement();
}
}
},
setDisabled: function (disabled) {
if (disabled) {
this._domElement.setAttribute("disabled", "disabled");
}
else {
this._domElement.removeAttribute("disabled");
}
},
_createSelectElement: function () {
var dataSourceLength = this._dataSource.getLength();
var text = "";
for (var i = 0; i < dataSourceLength; i++) {
var value = "" + this._dataSource.getValue(i);
var escaped = encodeHtml(value);
// WinRT localization often tags the strings with reading direction. We want this
// for display text (escaped), but don't want this in the value space, as it
// only present for display.
//
var stripped = stripDirectionMarker(escaped);
text += "";
}
WinJS.Utilities.setInnerHTMLUnsafe(this._domElement, text);
this._domElement.selectedIndex = this._index;
},
index: {
get: function () {
return Math.max(0, Math.min(this._index, this._dataSource.getLength() - 1));
},
set: function (value) {
if (this._index !== value) {
this._index = value;
var d = this._domElement;
if (d && d.selectedIndex !== value) {
d.selectedIndex = value;
}
}
}
},
value: {
get: function () {
return this._dataSource.getValue(this.index);
}
}
});
})
})
})(WinJS);
// Back Button
(function backButtonInit(WinJS) {
"use strict";
var nav = WinJS.Navigation;
// Class Names
var navigationBackButtonClass = 'win-navigation-backbutton';
var glyphClass = "win-back";
// CONSTANTS
var KEY_LEFT = "Left";
var KEY_BROWSER_BACK = "BrowserBack";
var MOUSE_BACK_BUTTON = 3;
// Create Singleton for global event registering/unregistering. This Singleton should only be created once.
// Here the function 'returnBackButtonSingelton' is called immediateley and its result is the singleton object.
var singleton = (function createBackButtonSingleton() {
/* Step 1: Build JavaScript closure */
function hookUpBackButtonGlobalEventHandlers() {
// Subscribes to global events on the window object
window.addEventListener('keyup', backButtonGlobalKeyUpHandler, false)
window.addEventListener('pointerup', backButtonGlobalMSPointerUpHandler, false);
}
function unHookBackButtonGlobalEventHandlers() {
// Unsubscribes from global events on the window object
window.removeEventListener('keyup', backButtonGlobalKeyUpHandler, false)
window.removeEventListener('pointerup', backButtonGlobalMSPointerUpHandler, false);
}
function backButtonGlobalKeyUpHandler(event) {
// Navigates back when (alt + left) or BrowserBack keys are released.
if ((event.key === KEY_LEFT && event.altKey && !event.shiftKey && !event.ctrlKey) || (event.key === KEY_BROWSER_BACK)) {
nav.back();
}
}
function backButtonGlobalMSPointerUpHandler(event) {
// Responds to clicks to enable navigation using 'back' mouse buttons.
if (event.button === MOUSE_BACK_BUTTON) {
nav.back();
}
}
// Singleton reference count for registering and unregistering global event handlers.
var backButtonReferenceCount = 0; //
/* Step 2: Return Singleton object literal */
return {
addRef: function () {
if (backButtonReferenceCount === 0) {
hookUpBackButtonGlobalEventHandlers();
}
backButtonReferenceCount++;
},
release: function () {
if (backButtonReferenceCount > 0) { // Ensure count won't become negative.
backButtonReferenceCount--;
if (backButtonReferenceCount === 0) {
unHookBackButtonGlobalEventHandlers();
}
}
},
getCount: function () { // Return the value of the reference count. Useful for unit testing.
return backButtonReferenceCount;
}
};
}()); // Immediate invoke creates and returns the Singleton
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Provides backwards navigation functionality.
///
///
///
///
///
/// ]]>
/// The BackButton control itself
/// The Back Arrow glyph
///
///
///
BackButton: WinJS.Namespace._lazy(function () {
// Statics
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/backbuttonarialabel").value; },
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; },
get badButtonElement() { return WinJS.Resources._getWinJSString("ui/badButtonElement").value; }
};
var BackButton = WinJS.Class.define(function BackButton_ctor(element, options) {
///
///
/// Creates a new BackButton control
///
///
/// The DOM element that will host the control. If this parameter is null, this constructor creates one for you.
///
///
/// An object that contains one or more property/value pairs to apply to the new control. Each property of the options object corresponds to
/// one of the control's properties or events.
///
///
/// A BackButton control.
///
///
///
// Check to make sure we weren't duplicated
if (element && element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.BackButton.DuplicateConstruction", strings.duplicateConstruction);
}
this._element = element || document.createElement("button");
options = options || {};
this._initializeButton(); // This will also set the aria-label and tooltip
this._disposed = false;
// Remember ourselves
this._element.winControl = this;
WinJS.UI.setOptions(this, options);
// Add event handlers for this back button instance
this._buttonClickHandler = this._handleBackButtonClick.bind(this);
this._element.addEventListener('click', this._buttonClickHandler, false);
this._navigatedHandler = this._handleNavigatedEvent.bind(this);
nav.addEventListener('navigated', this._navigatedHandler, false);
// Increment reference count / manage add global event handlers
singleton.addRef();
}, {
///
/// Gets the DOM element that hosts the BackButton control.
///
///
element: {
get: function () {
return this._element;
}
},
dispose: function () {
///
///
/// Disposes this control.
///
///
///
if (this._disposed) {
return;
}
this._disposed = true; // Mark this control as disposed.
// Remove 'navigated' eventhandler for this BackButton
nav.removeEventListener('navigated', this._navigatedHandler, false);
singleton.release(); // Decrement reference count.
},
refresh: function () {
///
///
/// Sets the 'disabled' attribute to correct the value based on the current navigation history stack.
///
///
///
if (nav.canGoBack) {
this._element.disabled = false;
} else {
this._element.disabled = true;
}
},
_initializeButton: function () {
//Final EN-US HTML should be:
//
//Button will automatically be disabled if WinJS.Navigation.history.canGoBack is false.
// Verify the HTML is a button
if (this._element.tagName !== "BUTTON") {
throw new WinJS.ErrorFromName("WinJS.UI.BackButton.BadButtonElement", strings.badButtonElement);
}
// Attach our css classes
WinJS.Utilities.addClass(this._element, navigationBackButtonClass);
// Attach disposable class.
WinJS.Utilities.addClass(this._element, "win-disposable");
// Create inner glyph element
this._element.innerHTML = '';
// Set the 'disabled' property to the correct value based on the current navigation history stack.
this.refresh();
// Set Aria-label and native tooltip to the same localized string equivalent of "Back"
this._element.setAttribute("aria-label", strings.ariaLabel);
this._element.setAttribute("title", strings.ariaLabel);
// Explicitly set type attribute to avoid the default ]]>
/// The AppBarCommand control itself.
/// The AppBarCommand's icon box.
/// The AppBarCommand's icon's image formatting.
/// The AppBarCommand's icon's ring.
/// The AppBarCommand's label
///
///
///
AppBarCommand: WinJS.Namespace._lazy(function () {
var thisWinUI = WinJS.UI;
// Class Names
var appBarCommandClass = "win-command",
appBarCommandGlobalClass = "win-global",
appBarCommandSelectionClass = "win-selection",
reducedClass = "win-reduced",
typeSeparator = "separator",
typeButton = "button",
typeToggle = "toggle",
typeFlyout = "flyout",
typeContent = "content",
sectionSelection = "selection",
sectionGlobal = "global";
function _handleClick(event) {
var command = this.winControl;
if (command) {
if (command._type === typeToggle) {
command.selected = !command.selected;
} else if (command._type === typeFlyout && command._flyout) {
var parentAppBar = thisWinUI._Overlay._getParentControlUsingClassName(this, "win-appbar");
var placement = "top";
if (parentAppBar && parentAppBar.placement === "top") {
placement = "bottom";
}
var flyout = command._flyout;
// Flyout may not have processAll'd, so this may be a DOM object
if (typeof flyout === "string") {
flyout = document.getElementById(flyout);
}
if (!flyout.show) {
flyout = flyout.winControl;
}
if (flyout && flyout.show) {
flyout.show(this, placement);
}
}
if (command.onclick) {
command.onclick(event);
}
}
}
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/appBarCommandAriaLabel").value; },
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; },
get badClick() { return WinJS.Resources._getWinJSString("ui/badClick").value; },
get badDivElement() { return WinJS.Resources._getWinJSString("ui/badDivElement").value; },
get badHrElement() { return WinJS.Resources._getWinJSString("ui/badHrElement").value; },
get badButtonElement() { return WinJS.Resources._getWinJSString("ui/badButtonElement").value; }
};
return WinJS.Class.define(function AppBarCommand_ctor(element, options) {
///
///
/// Creates a new AppBarCommand control.
///
///
/// The DOM element that will host the control. AppBarCommand will create one if null.
///
///
/// The set of properties and values to apply to the new AppBarCommand.
///
///
/// The new AppBarCommand control.
///
///
// Check to make sure we weren't duplicated
if (element && element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBarCommand.DuplicateConstruction", strings.duplicateConstruction);
}
this._disposed = false;
// Don't blow up if they didn't pass options
if (!options) {
options = {};
}
// Need a type before we can create our element
if (!options.type) {
this._type = typeButton;
}
// Go ahead and create it, separator and content types look different than buttons
// Don't forget to use passed in element if one was provided.
this._element = element;
if (options.type === typeContent) {
this._createContent();
}
else if (options.type === typeSeparator) {
this._createSeparator();
} else {
// This will also set the icon & label
this._createButton();
}
WinJS.Utilities.addClass(this._element, "win-disposable");
// Remember ourselves
this._element.winControl = this;
// Attach our css class
WinJS.Utilities.addClass(this._element, appBarCommandClass);
if (options.onclick) {
this.onclick = options.onclick;
}
// We want to handle some clicks
options.onclick = _handleClick;
WinJS.UI.setOptions(this, options);
if (!options.section) {
this._setSection(options.section);
}
if (this._type === typeToggle && !options.selected) {
this.selected = false;
}
// Set up pointerdown handler and clean up ARIA if needed
if (this._type !== typeSeparator) {
// Hide the modern focus rect on click or touch
var that = this;
this._element.addEventListener("pointerdown", function () { thisWinUI._Overlay._addHideFocusClass(that._element); }, false);
// Make sure we have an ARIA role
var role = this._element.getAttribute("role");
if (role === null || role === "" || role === undefined) {
if (this._type === typeToggle) {
role = "menuitemcheckbox";
} else if (this._type === typeContent) {
role = "group";
} else {
role = "menuitem";
}
this._element.setAttribute("role", role);
if (this._type === typeFlyout) {
this._element.setAttribute("aria-haspopup", true);
}
}
// Label should've been set by label, but if it was missed for some reason:
var label = this._element.getAttribute("aria-label");
if (label === null || label === "" || label === undefined) {
this._element.setAttribute("aria-label", strings.ariaLabel);
}
}
}, {
///
/// Gets or sets the ID of the AppBarCommand.
///
id: {
get: function () {
return this._element.id;
},
set: function (value) {
// we allow setting first time only. otherwise we ignore it.
if (value && !this._element.id) {
this._element.id = value;
}
}
},
///
/// Gets or sets the type of the AppBarCommand. Possible values are "button", "toggle", "flyout", or "separator".
///
type: {
get: function () {
return this._type;
},
set: function (value) {
// we allow setting first time only. otherwise we ignore it.
if (!this._type) {
if (value !== typeContent && value !== typeFlyout && value !== typeToggle && value !== typeSeparator) {
this._type = typeButton;
} else {
this._type = value;
}
}
}
},
///
/// Gets or sets the label of the AppBarCommand.
///
label: {
get: function () {
return this._label;
},
set: function (value) {
this._label = value;
if (this._labelSpan) {
this._labelSpan.innerText = this.label;
}
// Ensure that we have a tooltip, by updating already-constructed tooltips. Separators won't have these:
if (!this.tooltip && this._tooltipControl) {
this._tooltip = this.label;
this._tooltipControl.innerHTML = this.label;
}
// Update aria-label
this._element.setAttribute("aria-label", this.label);
// Check if we need to suppress the tooltip
this._testIdenticalTooltip();
}
},
///
/// Gets or sets the icon of the AppBarCommand.
///
icon: {
get: function () {
return this._icon;
},
set: function (value) {
if (value) {
this._icon = (WinJS.UI.AppBarIcon[value] || value);
} else {
this._icon = value;
}
if (this._imageSpan) {
// If the icon's a single character, presume a glyph
if (this._icon && this._icon.length === 1) {
// Set the glyph
this._imageSpan.innerText = this._icon;
this._imageSpan.style.backgroundImage = "";
this._imageSpan.style.msHighContrastAdjust = "";
} else {
// Must be an image, set that
this._imageSpan.innerText = "";
this._imageSpan.style.backgroundImage = this._icon;
this._imageSpan.style.msHighContrastAdjust = "none";
}
}
}
},
///
/// Gets or sets the function to invoke when the command is clicked.
///
onclick: {
get: function () {
return this._onclick;
},
set: function (value) {
if (value && typeof value !== "function") {
throw new WinJS.ErrorFromName("WinJS.UI.AppBarCommand.BadClick", WinJS.Resources._formatString(strings.badClick, "AppBarCommand"));
}
this._onclick = value;
}
},
///
/// For flyout-type AppBarCommands, this property returns the WinJS.UI.Flyout that this command invokes.
/// When setting this property, you may also use the String ID of the flyout to invoke, the DOM object
/// for the flyout, or the WinJS.UI.Flayout object itself.
///
flyout: {
get: function () {
// Resolve it to the flyout
var flyout = this._flyout;
if (typeof flyout === "string") {
flyout = document.getElementById(flyout);
}
// If it doesn't have a .element, then we need to getControl on it
if (flyout && !flyout.element) {
flyout = flyout.winControl;
}
return flyout;
},
set: function (value) {
// Need to update aria-owns with the new ID.
var id = value;
if (id && typeof id !== "string") {
// Our controls have .element properties
if (id.element) {
id = id.element;
}
// Hope it's a DOM element, get ID from DOM element
if (id) {
if (id.id) {
id = id.id;
} else {
// No id, have to fake one
id.id = id.uniqueID;
id = id.id;
}
}
}
if (typeof id === "string") {
this._element.setAttribute("aria-owns", id);
}
// Remember it
this._flyout = value;
}
},
///
/// Gets or sets the section that the AppBarCommand is in. Possible values are "selection" and "global".
///
section: {
get: function () {
return this._section;
},
set: function (value) {
// we allow settings section only one time
if (!this._section || (window.Windows && Windows.ApplicationModel && Windows.ApplicationModel.DesignMode && Windows.ApplicationModel.DesignMode.designModeEnabled)) {
this._setSection(value);
}
}
},
/// Gets or sets the tooltip text of the AppBarCommand.
tooltip: {
get: function () {
return this._tooltip;
},
set: function (value) {
this._tooltip = value;
// Update already-constructed tooltips. Separators and content commands won't have these:
if (this._tooltipControl) {
this._tooltipControl.innerHTML = this._tooltip;
}
// Check if we need to suppress the tooltip
this._testIdenticalTooltip();
}
},
/// Set or get the selected state of a toggle button.
selected: {
get: function () {
// Ensure it's a boolean because we're using the DOM element to keep in-sync
return this._element.getAttribute("aria-checked") === "true";
},
set: function (value) {
this._element.setAttribute("aria-checked", value);
}
},
///
/// The DOM element that hosts the AppBarCommad.
///
element: {
get: function () {
return this._element;
}
},
///
/// Gets or sets a value that indicates whether the AppBarCommand is disabled. A value of true disables the AppBarCommand, and a value of false enables it.
///
disabled: {
get: function () {
// Ensure it's a boolean because we're using the DOM element to keep in-sync
return !!this._element.disabled;
},
set: function (value) {
this._element.disabled = value;
}
},
///
/// Gets a value that indicates whether the AppBarCommand is hiding or in the process of becoming hidden.
/// A value of true indicates that the AppBarCommand is hiding or in the process of becoming hidden.
///
hidden: {
get: function () {
// Ensure it's a boolean because we're using the DOM element to keep in-sync
return this._element.style.visibility === "hidden";
},
set: function (value) {
var appbarControl = thisWinUI._Overlay._getParentControlUsingClassName(this._element, "win-appbar");
if (appbarControl && !appbarControl.hidden) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBarCommand.CannotChangeHiddenProperty", WinJS.Resources._formatString(thisWinUI._Overlay.commonstrings.cannotChangeHiddenProperty, "AppBar"));
}
if (value === this.hidden) {
// No changes to make.
return;
}
var style = this._element.style;
if (value) {
style.visibility = "hidden";
style.display = "none";
} else {
style.visibility = "";
style.display = "inline-block";
}
if (appbarControl) {
appbarControl._contentChanged();
}
}
},
///
/// Gets or sets the HTMLElement within a "content" type AppBarCommand that should receive focus whenever focus moves via Home or the arrow keys,
/// from the previous AppBarCommand to the this AppBarCommand. Returns the AppBarCommand object's host element by default.
///
firstElementFocus: {
get: function () {
return this._firstElementFocus || this._lastElementFocus || this._element;
},
set: function (element) {
// Arguments of null and this.element should treated the same to ensure that this.element is never a tabstop when either focus property has been set.
this._firstElementFocus = (element === this.element) ? null : element;
this._updateTabStop();
}
},
///
/// Gets or sets the HTMLElement within a "content" type AppBarCommand that should receive focus whenever focus would move, via End or arrow keys,
/// from the next AppBarCommand to this AppBarCommand. Returns this AppBarCommand object's host element by default.
///
lastElementFocus: {
get: function () {
return this._lastElementFocus || this._firstElementFocus || this._element;
},
set: function (element) {
// Arguments of null and this.element should treated the same to ensure that this.element is never a tabstop when either focus property has been set.
this._lastElementFocus = (element === this.element) ? null : element;
this._updateTabStop();
}
},
dispose: function () {
///
///
/// Disposes this control.
///
///
if (this._disposed) {
return;
}
this._disposed = true;
if (this._tooltipControl) {
this._tooltipControl.dispose();
}
if (this._type === typeContent) {
WinJS.Utilities.disposeSubTree(this.element);
}
},
addEventListener: function (type, listener, useCapture) {
///
///
/// Registers an event handler for the specified event.
///
///
/// Required. The name of the event to register. It must be "beforeshow", "beforehide", "aftershow", or "afterhide".
///
/// Required. The event handler function to associate with this event.
///
/// Optional. Set to true to register the event handler for the capturing phase; otherwise, set to false to register the event handler for the bubbling phase.
///
///
return this._element.addEventListener(type, listener, useCapture);
},
removeEventListener: function (type, listener, useCapture) {
///
///
/// Removes an event handler that the addEventListener method registered.
///
/// Required. The name of the event to remove.
/// Required. The event handler function to remove.
///
/// Optional. Set to true to remove the capturing phase event handler; otherwise, set to false to remove the bubbling phase event handler.
///
///
return this._element.removeEventListener(type, listener, useCapture);
},
/// Adds an extra CSS class during construction.
extraClass: {
get: function () {
return this._extraClass;
},
set: function (value) {
if (this._extraClass) {
WinJS.Utilities.removeClass(this._element, this._extraClass);
}
this._extraClass = value;
WinJS.Utilities.addClass(this._element, this._extraClass);
}
},
// Private
_testIdenticalTooltip: function AppBarCommand_testIdenticalToolTip() {
this._hideIfFullSize = (this._label === this._tooltip);
},
_createContent: function AppBarCommand_createContent() {
// Make sure there's an element
if (!this._element) {
this._element = document.createElement("div");
} else {
// Verify the element was a div
if (this._element.tagName !== "DIV") {
throw new WinJS.ErrorFromName("WinJS.UI.AppBarCommand.BadDivElement", strings.badDivElement);
}
}
// If a tabIndex isnt set, default to 0;
if (parseInt(this._element.getAttribute("tabIndex"), 10) !== this._element.tabIndex) {
this._element.tabIndex = 0;
}
},
_createSeparator: function AppBarCommand_createSeparator() {
// Make sure there's an element
if (!this._element) {
this._element = document.createElement("hr");
} else {
// Verify the element was an hr
if (this._element.tagName !== "HR") {
throw new WinJS.ErrorFromName("WinJS.UI.AppBarCommand.BadHrElement", strings.badHrElement);
}
}
},
_createButton: function AppBarCommand_createButton() {
// Make sure there's an element
if (!this._element) {
this._element = document.createElement("button");
} else {
// Verify the element was a button
if (this._element.tagName !== "BUTTON") {
throw new WinJS.ErrorFromName("WinJS.UI.AppBarCommand.BadButtonElement", strings.badButtonElement);
}
// Make sure it has a type="button"
var type = this._element.getAttribute("type");
if (type === null || type === "" || type === undefined) {
this._element.setAttribute("type", "button");
}
this._element.innerHTML = "";
}
// AppBarCommand buttons need to look like this:
////
//// Command 1
//// Or This:
//// Command 1
////
this._element.type = "button";
this._iconSpan = document.createElement("span");
this._iconSpan.setAttribute("aria-hidden", "true");
this._iconSpan.className = "win-commandicon win-commandring";
this._iconSpan.tabIndex = -1;
this._element.appendChild(this._iconSpan);
this._imageSpan = document.createElement("span");
this._imageSpan.setAttribute("aria-hidden", "true");
this._imageSpan.className = "win-commandimage";
this._imageSpan.tabIndex = -1;
this._iconSpan.appendChild(this._imageSpan);
this._labelSpan = document.createElement("span");
this._labelSpan.setAttribute("aria-hidden", "true");
this._labelSpan.className = "win-label";
this._labelSpan.tabIndex = -1;
this._element.appendChild(this._labelSpan);
// 'win-global' or 'win-selection' are added later by caller.
// Label and icon are added later by caller.
// Attach a tooltip - Note: we're going to stomp on it's setControl so we don't have to make another DOM element to hang it off of.
// This private _tooltipControl attribute is used by other pieces, changing the name could break them.
this._tooltipControl = new WinJS.UI.Tooltip(this._element);
var that = this;
this._tooltipControl.addEventListener("beforeopen", function () {
if (that._hideIfFullSize && !thisWinUI._Overlay._getParentControlUsingClassName(that._element.parentElement, reducedClass)) {
that._tooltipControl.close();
}
}, false);
},
_setSection: function AppBarCommand_setSection(section) {
if (!section) {
section = sectionGlobal;
}
if (this._section) {
// Remove the old section class
if (this._section === sectionGlobal) {
WinJS.Utilities.removeClass(this._element, appBarCommandGlobalClass);
} else if (this.section === sectionSelection) {
WinJS.Utilities.removeClass(this._element, appBarCommandSelectionClass);
}
}
// Add the new section class
this._section = section;
if (section === sectionGlobal) {
WinJS.Utilities.addClass(this._element, appBarCommandGlobalClass);
} else if (section === sectionSelection) {
WinJS.Utilities.addClass(this._element, appBarCommandSelectionClass);
}
},
_updateTabStop: function AppBarCommand_updateTabStop() {
// Whenever the firstElementFocus or lastElementFocus properties are set for content type AppBarCommands,
// the containing command element is no longer a tabstop.
if (this._firstElementFocus || this._lastElementFocus ) {
this.element.tabIndex = -1;
} else {
this.element.tabIndex = 0;
}
},
_isFocusable: function AppBarCommand_isFocusable() {
return (!this.hidden && this._type !== typeSeparator && !this.element.disabled &&
(this.firstElementFocus.tabIndex >= 0 || this.lastElementFocus.tabIndex >= 0))
},
});
})
});
})(WinJS);
// AppBar
/// appbar,appBars,Flyout,Flyouts,iframe,Statics,unfocus,WinJS
(function appBarInit(WinJS) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Represents an application toolbar for display commands.
///
///
///
///
///
/// ]]>
/// Raised just before showing the AppBar.
/// Raised immediately after the AppBar is fully shown.
/// Raised just before hiding the AppBar.
/// Raised immediately after the AppBar is fully hidden.
/// The AppBar control itself.
/// Style for a custom layout AppBar.
///
///
///
AppBar: WinJS.Namespace._lazy(function () {
var thisWinUI = WinJS.UI;
// Class Names
var commandClass = "win-commandlayout",
appBarClass = "win-appbar",
reducedClass = "win-reduced",
settingsFlyoutClass = "win-settingsflyout",
topClass = "win-top",
bottomClass = "win-bottom",
appBarCommandClass = "win-command";
var firstDivClass = "win-firstdiv",
finalDivClass = "win-finaldiv";
// Constants for placement
var appBarPlacementTop = "top",
appBarPlacementBottom = "bottom";
// Constants for layout
var appBarLayoutCustom = "custom",
appBarLayoutCommands = "commands";
// Constants for AppBarCommands
var typeSeparator = "separator",
typeContent = "content",
separatorWidth = 60,
buttonWidth = 100;
// Hook into event
var appBarCommandEvent = false;
var edgyHappening = null;
// Handler for the edgy starting/completed/cancelled events
function _completedEdgy(e) {
// If we had a right click on a flyout, ignore it.
if (thisWinUI._Overlay._rightMouseMightEdgy &&
e.kind === Windows.UI.Input.EdgeGestureKind.mouse) {
return;
}
if (edgyHappening) {
// Edgy was happening, just skip it
edgyHappening = null;
} else {
// Edgy wasn't happening, so toggle
var keyboardInvoked = e.kind === Windows.UI.Input.EdgeGestureKind.keyboard;
WinJS.UI.AppBar._toggleAppBarEdgy(keyboardInvoked);
}
}
function _startingEdgy() {
if (!edgyHappening) {
// Edgy wasn't happening, so toggle & start it
edgyHappening = WinJS.UI.AppBar._toggleAppBarEdgy(false);
}
}
function _canceledEdgy() {
// Shouldn't get here unless edgy was happening.
// Undo whatever we were doing.
var bars = _getDynamicBarsForEdgy();
if (edgyHappening === "showing") {
_hideAllBars(bars, false);
} else if (edgyHappening === "hiding") {
_showAllBars(bars, false);
}
edgyHappening = null;
}
function _allManipulationChanged(event) {
var elements = document.querySelectorAll("." + appBarClass);
if (elements) {
var len = elements.length;
for (var i = 0; i < len; i++) {
var element = elements[i];
var appbar = element.winControl;
if (appbar && !element.disabled) {
appbar._manipulationChanged(event);
}
}
}
}
// Get all the non-sticky bars and return them.
// Returns array of AppBar objects.
// The array also has _hidden and/or _visible set if ANY are hidden or visible.
function _getDynamicBarsForEdgy() {
var elements = document.querySelectorAll("." + appBarClass);
var len = elements.length;
var AppBars = [];
AppBars._visible = false;
AppBars._hidden = false;
for (var i = 0; i < len; i++) {
var element = elements[i];
if (element.disabled) {
// Skip disabled AppBars
continue;
}
var AppBar = element.winControl;
if (AppBar) {
AppBars.push(AppBar);
// Middle of animation is different than animated
if (AppBar._element.winAnimating) {
// If animating, look at showing/hiding
if (AppBar._element.winAnimating === "hiding") {
AppBars._hidden = true;
} else {
AppBars._visible = true;
}
} else {
// Not animating, so check visibility
if (AppBar._element.style.visibility === "hidden") {
AppBars._hidden = true;
} else {
AppBars._visible = true;
}
}
}
}
return AppBars;
}
// Show or hide all bars
function _hideAllBars(bars, keyboardInvoked) {
var len = bars.length;
var allBarsAnimationPromises = new Array(len);
for (var i = 0; i < len; i++) {
bars[i]._keyboardInvoked = keyboardInvoked;
bars[i].hide();
allBarsAnimationPromises[i] = bars[i]._animationPromise;
}
return WinJS.Promise.join(allBarsAnimationPromises);
}
function _showAllBars(bars, keyboardInvoked) {
var len = bars.length;
var allBarsAnimationPromises = new Array(len);
for (var i = 0; i < len; i++) {
bars[i]._keyboardInvoked = keyboardInvoked;
bars[i]._doNotFocus = false;
bars[i]._show();
allBarsAnimationPromises[i] = bars[i]._animationPromise;
}
return WinJS.Promise.join(allBarsAnimationPromises);
}
// Sets focus to the last AppBar in the provided appBars array with given placement.
// Returns true if focus was set. False otherwise.
function _setFocusToPreviousAppBarHelper(startIndex, appBarPlacement, appBars) {
for (var i = startIndex; i >= 0; i--) {
if (appBars[i].winControl
&& appBars[i].winControl.placement === appBarPlacement
&& !appBars[i].winControl.hidden
&& appBars[i].winControl._focusOnLastFocusableElement
&& appBars[i].winControl._focusOnLastFocusableElement()) {
return true;
}
}
return false;
}
// Sets focus to the last AppBar in the provided appBars array with other placement.
// Returns true if focus was set. False otherwise.
function _setFocusToPreviousAppBarHelperNeither(startIndex, appBars) {
for (var i = startIndex; i >= 0; i--) {
if (appBars[i].winControl
&& appBars[i].winControl.placement != appBarPlacementBottom
&& appBars[i].winControl.placement != appBarPlacementTop
&& !appBars[i].winControl.hidden
&& appBars[i].winControl._focusOnLastFocusableElement
&& appBars[i].winControl._focusOnLastFocusableElement()) {
return true;
}
}
return false;
}
// Sets focus to the last tab stop of the previous AppBar
// AppBar tabbing order:
// 1) Bottom AppBars
// 2) Top AppBars
// 3) Other AppBars
// DOM order is respected, because an AppBar should not have a defined tabIndex
function _setFocusToPreviousAppBar() {
var appBars = document.querySelectorAll("." + appBarClass);
if (!appBars.length) {
return;
}
var thisAppBarIndex = 0;
for (var i = 0; i < appBars.length; i++) {
if (appBars[i] === this.parentElement) {
thisAppBarIndex = i;
break;
}
}
var appBarControl = this.parentElement.winControl;
if (appBarControl.placement === appBarPlacementBottom) {
// Bottom appBar: Focus order: (1)previous bottom appBars (2)other appBars (3)top appBars (4)bottom appBars
if (thisAppBarIndex && _setFocusToPreviousAppBarHelper(thisAppBarIndex - 1, appBarPlacementBottom, appBars)) { return; }
if (_setFocusToPreviousAppBarHelperNeither(appBars.length - 1, appBars)) { return; }
if (_setFocusToPreviousAppBarHelper(appBars.length - 1, appBarPlacementTop, appBars)) { return; }
if (_setFocusToPreviousAppBarHelper(appBars.length - 1, appBarPlacementBottom, appBars)) { return; }
} else if (appBarControl.placement === appBarPlacementTop) {
// Top appBar: Focus order: (1)previous top appBars (2)bottom appBars (3)other appBars (4)top appBars
if (thisAppBarIndex && _setFocusToPreviousAppBarHelper(thisAppBarIndex - 1, appBarPlacementTop, appBars)) { return; }
if (_setFocusToPreviousAppBarHelper(appBars.length - 1, appBarPlacementBottom, appBars)) { return; }
if (_setFocusToPreviousAppBarHelperNeither(appBars.length - 1, appBars)) { return; }
if (_setFocusToPreviousAppBarHelper(appBars.length - 1, appBarPlacementTop, appBars)) { return; }
} else {
// Other appBar: Focus order: (1)previous other appBars (2)top appBars (3)bottom appBars (4)other appBars
if (thisAppBarIndex && _setFocusToPreviousAppBarHelperNeither(thisAppBarIndex - 1, appBars)) { return; }
if (_setFocusToPreviousAppBarHelper(appBars.length - 1, appBarPlacementTop, appBars)) { return; }
if (_setFocusToPreviousAppBarHelper(appBars.length - 1, appBarPlacementBottom, appBars)) { return; }
if (_setFocusToPreviousAppBarHelperNeither(appBars.length - 1, appBars)) { return; }
}
}
// Sets focus to the first AppBar in the provided appBars array with given placement.
// Returns true if focus was set. False otherwise.
function _setFocusToNextAppBarHelper(startIndex, appBarPlacement, appBars) {
for (var i = startIndex; i < appBars.length; i++) {
if (appBars[i].winControl
&& appBars[i].winControl.placement === appBarPlacement
&& !appBars[i].winControl.hidden
&& appBars[i].winControl._focusOnFirstFocusableElement
&& appBars[i].winControl._focusOnFirstFocusableElement()) {
return true;
}
}
return false;
}
// Sets focus to the first AppBar in the provided appBars array with other placement.
// Returns true if focus was set. False otherwise.
function _setFocusToNextAppBarHelperNeither(startIndex, appBars) {
for (var i = startIndex; i < appBars.length; i++) {
if (appBars[i].winControl
&& appBars[i].winControl.placement != appBarPlacementBottom
&& appBars[i].winControl.placement != appBarPlacementTop
&& !appBars[i].winControl.hidden
&& appBars[i].winControl._focusOnFirstFocusableElement
&& appBars[i].winControl._focusOnFirstFocusableElement()) {
return true;
}
}
return false;
}
// Sets focus to the first tab stop of the next AppBar
// AppBar tabbing order:
// 1) Bottom AppBars
// 2) Top AppBars
// 3) Other AppBars
// DOM order is respected, because an AppBar should not have a defined tabIndex
function _setFocusToNextAppBar() {
var appBars = document.querySelectorAll("." + appBarClass);
var thisAppBarIndex = 0;
for (var i = 0; i < appBars.length; i++) {
if (appBars[i] === this.parentElement) {
thisAppBarIndex = i;
break;
}
}
var appBarControl = this.parentElement.winControl;
if (this.parentElement.winControl.placement === appBarPlacementBottom) {
// Bottom appBar: Focus order: (1)next bottom appBars (2)top appBars (3)other appBars (4)bottom appBars
if (_setFocusToNextAppBarHelper(thisAppBarIndex + 1, appBarPlacementBottom, appBars)) { return; }
if (_setFocusToNextAppBarHelper(0, appBarPlacementTop, appBars)) { return; }
if (_setFocusToNextAppBarHelperNeither(0, appBars)) { return; }
if (_setFocusToNextAppBarHelper(0, appBarPlacementBottom, appBars)) { return; }
} else if (this.parentElement.winControl.placement === appBarPlacementTop) {
// Top appBar: Focus order: (1)next top appBars (2)other appBars (3)bottom appBars (4)top appBars
if (_setFocusToNextAppBarHelper(thisAppBarIndex + 1, appBarPlacementTop, appBars)) { return; }
if (_setFocusToNextAppBarHelperNeither(0, appBars)) { return; }
if (_setFocusToNextAppBarHelper(0, appBarPlacementBottom, appBars)) { return; }
if (_setFocusToNextAppBarHelper(0, appBarPlacementTop, appBars)) { return; }
} else {
// Other appBar: Focus order: (1)next other appBars (2)bottom appBars (3)top appBars (4)other appBars
if (_setFocusToNextAppBarHelperNeither(thisAppBarIndex + 1, appBars)) { return; }
if (_setFocusToNextAppBarHelper(0, appBarPlacementBottom, appBars)) { return; }
if (_setFocusToNextAppBarHelper(0, appBarPlacementTop, appBars)) { return; }
if (_setFocusToNextAppBarHelperNeither(0, appBars)) { return; }
}
}
// Updates the firstDiv & finalDiv of all visible AppBars
function _updateAllAppBarsFirstAndFinalDiv() {
var appBars = document.querySelectorAll("." + appBarClass);
for (var i = 0; i < appBars.length; i++) {
if (appBars[i].winControl
&& !appBars[i].winControl.hidden
&& appBars[i].winControl._updateFirstAndFinalDiv) {
appBars[i].winControl._updateFirstAndFinalDiv();
}
}
}
// Returns true if a visible non-sticky (light dismiss) AppBar is found in the document
function _isThereVisibleNonStickyBar() {
var appBars = document.querySelectorAll("." + appBarClass);
for (var i = 0; i < appBars.length; i++) {
var appBarControl = appBars[i].winControl;
if (appBarControl && !appBarControl.sticky &&
(!appBarControl.hidden || appBarControl._element.winAnimating === "showing")) {
return true;
}
}
return false;
}
// Hide all light dismiss AppBars if what has focus is not part of a AppBar or flyout.
function _hideIfAllAppBarsLostFocus() {
if (!thisWinUI.AppBar._isAppBarOrChild(document.activeElement)) {
thisWinUI.AppBar._hideLightDismissAppBars(null, false);
// Ensure that sticky appbars clear cached focus after light dismiss are dismissed, which moved focus.
thisWinUI.AppBar._ElementWithFocusPreviousToAppBar = null;
}
}
// If the previous focus was not a AppBar or CED, store it in the cache
// (_isAppBarOrChild tests CED for us).
function _checkStorePreviousFocus(focusEvent) {
if (focusEvent.relatedTarget
&& focusEvent.relatedTarget.focus
&& !thisWinUI.AppBar._isAppBarOrChild(focusEvent.relatedTarget)) {
_storePreviousFocus(focusEvent.relatedTarget);
}
}
// Cache the previous focus information
function _storePreviousFocus(element) {
if (element) {
thisWinUI.AppBar._ElementWithFocusPreviousToAppBar = element;
}
}
// Try to return focus to what had focus before.
// If successfully return focus to a textbox, restore the selection too.
function _restorePreviousFocus() {
thisWinUI._Overlay._trySetActive(thisWinUI.AppBar._ElementWithFocusPreviousToAppBar);
}
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/appBarAriaLabel").value; },
get requiresCommands() { return WinJS.Resources._getWinJSString("ui/requiresCommands").value; },
get nullCommand() { return WinJS.Resources._getWinJSString("ui/nullCommand").value; },
get cannotChangePlacementWhenVisible() { return WinJS.Resources._getWinJSString("ui/cannotChangePlacementWhenVisible").value; },
get badLayout() { return WinJS.Resources._getWinJSString("ui/badLayout").value; },
get cannotChangeLayoutWhenVisible() { return WinJS.Resources._getWinJSString("ui/cannotChangeLayoutWhenVisible").value; }
};
var AppBar = WinJS.Class.derive(WinJS.UI._Overlay, function AppBar_ctor(element, options) {
///
///
/// Creates a new AppBar control.
///
///
/// The DOM element that will host the control.
///
///
/// The set of properties and values to apply to the new AppBar control.
///
///
/// The new AppBar control.
///
///
this._initializing = true;
// Simplify checking later
options = options || {};
// Make sure there's an input element
this._element = element || document.createElement("div");
this._id = this._element.id || this._element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
// validate that if they didn't set commands, but want command
// layout that the HTML only contains commands. Do this first
// so that we don't leave partial AppBars in the DOM.
if (options.layout !== appBarLayoutCustom && !options.commands && this._element) {
// Shallow copy object so we can modify it.
options = WinJS.Utilities._shallowCopy(options);
options.commands = this._verifyCommandsOnly(this._element, "WinJS.UI.AppBarCommand");
}
// Call the base overlay constructor helper
this._baseOverlayConstructor(this._element, options);
this._initializing = false;
// Make a click eating div
thisWinUI._Overlay._createClickEatingDivAppBar();
// Attach our css class,
WinJS.Utilities.addClass(this._element, appBarClass);
// Also may need a command class if not a custom layout appbar
if (options.layout !== appBarLayoutCustom) {
WinJS.Utilities.addClass(this._element, commandClass);
}
if (!options.placement) {
// Make sure we have default placement
this.placement = appBarPlacementBottom;
}
// Make sure we have an ARIA role
var role = this._element.getAttribute("role");
if (!role) {
this._element.setAttribute("role", "menubar");
}
var label = this._element.getAttribute("aria-label");
if (!label) {
this._element.setAttribute("aria-label", strings.ariaLabel);
}
// Handle key down (esc) and key pressed (left & right)
this._element.addEventListener("keydown", this._handleKeyDown.bind(this), false);
// Attach event handler
if (!appBarCommandEvent) {
// We'll trigger on invoking. Could also have invoked or canceled
// Eventually we may want click up on invoking and drop back on invoked.
// Check for namespace so it'll behave in the designer.
if (WinJS.Utilities.hasWinRT) {
var commandUI = Windows.UI.Input.EdgeGesture.getForCurrentView();
commandUI.addEventListener("starting", _startingEdgy);
commandUI.addEventListener("completed", _completedEdgy);
commandUI.addEventListener("canceled", _canceledEdgy);
}
// Need to know if the IHM is done scrolling
document.addEventListener("MSManipulationStateChanged", _allManipulationChanged, false);
appBarCommandEvent = true;
}
// Make sure _Overlay event handlers are hooked up (this aids light dismiss)
this._addOverlayEventHandlers(false);
// Need to store what had focus before
this._element.addEventListener("focusin", function (event) { _checkStorePreviousFocus(event); }, false);
// Need to hide ourselves if we lose focus
this._element.addEventListener("focusout", function (event) { _hideIfAllAppBarsLostFocus(); }, false);
// Commands layout AppBar measures and caches its content synchronously in setOptions through the .commands property setter.
// Remove the commands layout AppBar from the layout tree at this point so we don't cause unnecessary layout costs whenever
// the window resizes or when CSS changes are applied to the commands layout AppBar's parent element.
if (this.layout === appBarLayoutCommands) {
this._element.style.display = "none";
}
this._writeProfilerMark("constructor,StopTM");
return this;
}, {
// Public Properties
/// The placement of the AppBar on the display. Values are "top" or "bottom".
placement: {
get: function () {
return this._placement;
},
set: function (value) {
// In designer we may have to move it
var wasShown = false;
if (window.Windows && Windows.ApplicationModel && Windows.ApplicationModel.DesignMode && Windows.ApplicationModel.DesignMode.designModeEnabled && !this.hidden) {
this._hide();
wasShown = true;
}
if (!this.hidden) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.CannotChangePlacementWhenVisible", strings.cannotChangePlacementWhenVisible);
}
// Set placement
this._placement = value;
// Clean up win-top, win-bottom styles
if (this._placement === appBarPlacementTop) {
WinJS.Utilities.addClass(this._element, topClass);
WinJS.Utilities.removeClass(this._element, bottomClass);
} else if (this._placement === appBarPlacementBottom) {
WinJS.Utilities.removeClass(this._element, topClass);
WinJS.Utilities.addClass(this._element, bottomClass);
} else {
WinJS.Utilities.removeClass(this._element, topClass);
WinJS.Utilities.removeClass(this._element, bottomClass);
}
// Make sure our animations are correct
this._assignAnimations();
// Show again if we hid ourselves for the designer
if (wasShown) {
this._show();
}
}
},
///
/// Gets or sets the layout of the AppBar contents to either "commands" or "custom".
///
layout: {
get: function () {
// Defaults to commands if not set
return this._layout ? this._layout : appBarLayoutCommands;
},
set: function (value) {
if (value !== appBarLayoutCommands && value !== appBarLayoutCustom) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.BadLayout", strings.badLayout);
}
// In designer we may have to redraw it
var wasShown = false;
if (window.Windows && Windows.ApplicationModel && Windows.ApplicationModel.DesignMode && Windows.ApplicationModel.DesignMode.designModeEnabled && !this.hidden) {
this._hide();
wasShown = true;
}
if (!this.hidden) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.CannotChangeLayoutWhenVisible", strings.cannotChangeLayoutWhenVisible);
}
// Set layout
this._layout = value;
// Update our classes
if (this._layout === appBarLayoutCommands) {
// Add the appbar css class back
WinJS.Utilities.addClass(this._element, commandClass);
} else {
// Remove the appbar css class
WinJS.Utilities.removeClass(this._element, commandClass);
}
// Show again if we hid ourselves for the designer
if (wasShown) {
this._show();
}
},
configurable: true
},
///
/// Gets or sets value that indicates whether the AppBar is sticky.
/// This value is true if the AppBar is sticky; otherwise, it's false.
///
sticky: {
get: function () {
return this._sticky;
},
set: function (value) {
// If it doesn't change, do nothing
if (this._sticky === !!value) {
return;
}
this._sticky = !!value;
// Note: caller has to call .show() if they also want it visible
// Show or hide the click eating div based on sticky value
if (!this.hidden && this._element.style.visibility === "visible") {
// May have changed sticky state for keyboard navigation
_updateAllAppBarsFirstAndFinalDiv();
// Ensure that the click eating div is in the correct state
if (this._sticky) {
if (!_isThereVisibleNonStickyBar()) {
thisWinUI._Overlay._hideClickEatingDivAppBar();
}
} else {
thisWinUI._Overlay._showClickEatingDivAppBar();
if (this._shouldStealFocus()) {
_storePreviousFocus(document.activeElement);
this._setFocusToAppBar();
}
}
}
}
},
///
/// Sets the commands for the AppBar. This property accepts an array of AppBarCommand objects.
///
commands: {
set: function (value) {
// Fail if trying to set when visible
if (!this.hidden) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.CannotChangeCommandsWhenVisible", WinJS.Resources._formatString(thisWinUI._Overlay.commonstrings.cannotChangeCommandsWhenVisible, "AppBar"));
}
// Start from scratch
if (!this._initializing) {
// AppBarCommands defined in markup don't want to be disposed during initialization.
this._disposeChildren();
}
WinJS.Utilities.empty(this._element);
// In case they had only one...
if (!Array.isArray(value)) {
value = [value];
}
// Add commands
var len = value.length;
for (var i = 0; i < len; i++) {
this._addCommand(value[i]);
}
// Need to measure all content commands after they have been added to the AppBar to make sure we allow
// user defined CSS rules based on the ancestor of the content command to take affect.
this._needToMeasure = true;
// In case this is called from the constructor we wait for the AppBar to get added to the DOM.
// It should be added in the synchronous block in which the constructor was called.
WinJS.Utilities.Scheduler.schedule(this._layoutCommands, WinJS.Utilities.Scheduler.Priority.idle, this, "WinJS.AppBar._layoutCommands");
}
},
getCommandById: function (id) {
///
///
/// Retrieves the command with the specified ID from this AppBar.
/// If more than one command is found, this method returns them all.
///
/// Id of the command to return.
///
/// The command found, an array of commands if more than one have the same ID, or null if no command is found.
///
///
var commands = this.element.querySelectorAll("#" + id);
for (var count = 0; count < commands.length; count++) {
// Any elements we generated this should succeed for,
// but remove missing ones just to be safe.
commands[count] = commands[count].winControl;
if (!commands[count]) {
commands.splice(count, 1);
}
}
if (commands.length === 1) {
return commands[0];
} else if (commands.length === 0) {
return null;
}
return commands;
},
showCommands: function (commands) {
///
///
/// Show the specified commands of the AppBar.
///
///
/// An array of the commands to show. The array elements may be AppBarCommand objects, or the string identifiers (IDs) of commands.
///
///
if (!commands) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.RequiresCommands", strings.requiresCommands);
}
this._showCommands(commands);
},
hideCommands: function (commands) {
///
///
/// Hides the specified commands of the AppBar.
///
/// Required. Command or Commands to hide, either String, DOM elements, or WinJS objects.
///
if (!commands) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.RequiresCommands", strings.requiresCommands);
}
this._hideCommands(commands);
},
showOnlyCommands: function (commands) {
///
///
/// Show the specified commands, hiding all of the others in the AppBar.
///
///
/// An array of the commands to show. The array elements may be AppBarCommand objects, or the string identifiers (IDs) of commands.
///
///
if (!commands) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.RequiresCommands", strings.requiresCommands);
}
this._showOnlyCommands(commands);
},
show: function () {
///
///
/// Shows the AppBar, if hidden, regardless of other state
///
///
// Just wrap the private one, turning off keyboard invoked flag
this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
this._keyboardInvoked = false;
this._doNotFocus = !!this.sticky;
this._show();
},
_show: function AppBar_show() {
// Don't do anything if disabled
if (this.disabled) {
return;
}
// Make sure everything fits before showing
this._layoutCommands();
this._scaleAppBar();
// If we're covered by a keyboard we look hidden, so we may have to jump up
if (this._keyboardObscured) {
// just make us look hidden so that show() gets called.
this._fakeHide = true;
this._keyboardObscured = false;
}
// Regardless we're going to be in a CED state
if (!this.sticky) {
// Need click-eating div to be visible ASAP.
thisWinUI._Overlay._showClickEatingDivAppBar();
}
// If we are already animating, just remember this for later
if (this._element.winAnimating) {
this._doNext = "show";
return false;
}
// We call our base _baseShow because AppBar may need to override show
// "hiding" would need to cancel.
this._baseShow();
// Clean up tabbing behavior by making sure first and final divs are correct after showing.
if (!this.sticky && _isThereVisibleNonStickyBar()) {
_updateAllAppBarsFirstAndFinalDiv();
} else {
this._updateFirstAndFinalDiv();
}
// Check if we should steal focus
if (!this._doNotFocus && this._shouldStealFocus()) {
// Store what had focus if nothing currently is stored
if (!thisWinUI.AppBar._ElementWithFocusPreviousToAppBar) {
_storePreviousFocus(document.activeElement);
}
this._setFocusToAppBar();
}
},
hide: function () {
///
///
/// Hides the AppBar.
///
///
// Just wrap the private one
this._writeProfilerMark("hide,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndHide().
this._hide();
},
_hide: function AppBar_hide() {
// If we're covered by a keyboard we already look hidden
if (this._keyboardObscured && !this._animating) {
this._keyboardObscured = false;
this._baseEndHide();
} else {
// We call our base "_baseHide" because AppBar may need to override hide
this._baseHide();
}
// Determine if there are any AppBars that are visible.
// Set the focus to the next visible AppBar.
// If there are none, set the focus to the control stored in the cache, which
// is what had focus before the AppBars were given focus.
var appBars = document.querySelectorAll("." + appBarClass);
var areOtherAppBars = false;
var areOtherNonStickyAppBars = false;
var i;
for (i = 0; i < appBars.length; i++) {
var appBarControl = appBars[i].winControl;
if (appBarControl && !appBarControl.hidden && (appBarControl !== this)) {
areOtherAppBars = true;
if (!appBarControl.sticky) {
areOtherNonStickyAppBars = true;
break;
}
}
}
var settingsFlyouts = document.querySelectorAll("." + settingsFlyoutClass);
var areVisibleSettingsFlyouts = false;
for (i = 0; i < settingsFlyouts.length; i++) {
var settingsFlyoutControl = settingsFlyouts[i].winControl;
if (settingsFlyoutControl && !settingsFlyoutControl.hidden) {
areVisibleSettingsFlyouts = true;
break;
}
}
if (!areOtherNonStickyAppBars && !areVisibleSettingsFlyouts) {
// Hide the click eating div because there are no other AppBars showing
thisWinUI._Overlay._hideClickEatingDivAppBar();
}
var that = this;
if (!areOtherAppBars) {
// Set focus to what had focus before showing the AppBar
if (thisWinUI.AppBar._ElementWithFocusPreviousToAppBar &&
(!document.activeElement || thisWinUI.AppBar._isAppBarOrChild(document.activeElement))) {
_restorePreviousFocus();
}
// Always clear the previous focus (to prevent temporary leaking of element)
thisWinUI.AppBar._ElementWithFocusPreviousToAppBar = null;
} else if (thisWinUI.AppBar._isWithinAppBarOrChild(document.activeElement, that.element)) {
// Set focus to next visible AppBar in DOM
var foundCurrentAppBar = false;
for (i = 0; i <= appBars.length; i++) {
if (i === appBars.length) {
i = 0;
}
var appBar = appBars[i];
if (appBar === this.element) {
foundCurrentAppBar = true;
} else if (foundCurrentAppBar && !appBar.winControl.hidden) {
appBar.winControl._keyboardInvoked = !!this._keyboardInvoked;
appBar.winControl._setFocusToAppBar();
break;
}
}
}
// If we are hiding the last lightDismiss AppBar,
// then we need to update the tabStops of the other AppBars
if (!this.sticky && !_isThereVisibleNonStickyBar()) {
_updateAllAppBarsFirstAndFinalDiv();
}
// Reset these values
this._keyboardInvoked = false;
this._doNotFocus = false;
},
_dispose: function AppBar_dispose() {
WinJS.Utilities.disposeSubTree(this.element);
this._hide();
},
_disposeChildren: function AppBar_disposeChildren() {
var appBarFirstDiv = this._element.querySelectorAll("." + firstDivClass);
appBarFirstDiv = appBarFirstDiv.length >= 1 ? appBarFirstDiv[0] : null;
var appBarFinalDiv = this._element.querySelectorAll("." + finalDivClass);
appBarFinalDiv = appBarFinalDiv.length >= 1 ? appBarFinalDiv[0] : null;
var children = this.element.children;
var length = children.length;
for (var i = 0; i < length; i++) {
var element = children[i];
if (element === appBarFirstDiv || element === appBarFinalDiv) {
continue;
}
if (this.layout === appBarLayoutCommands) {
element.winControl.dispose();
} else {
WinJS.Utilities.disposeSubTree(element);
}
}
},
_handleKeyDown: function AppBar_handleKeyDown(event) {
// On Left/Right arrow keys, moves focus to previous/next AppbarCommand element.
// On "Esc" key press hide all flyouts and light dismiss AppBars.
// Esc closes light-dismiss AppBars in all layouts but if the user has a text box with an IME
// candidate window open, we want to skip the ESC key event since it is handled by the IME.
// When the IME handles a key it sets event.keyCode to 229 for an easy check.
if (event.key === "Esc" && event.keyCode !== 229) {
event.preventDefault();
event.stopPropagation();
thisWinUI._Overlay._hideAllFlyouts();
thisWinUI.AppBar._hideLightDismissAppBars(null, true);
}
// Commands layout only.
if (this.layout === appBarLayoutCommands && !event.altKey) {
if (event.srcElement.msMatchesSelector(".win-interactive, .win-interactive *")) {
return; //ignore left, right, home & end keys if focused element has win-interactive class.
}
var rtl = getComputedStyle(this._element).direction === "rtl";
var leftKey = rtl ? "Right" : "Left";
var rightKey = rtl ? "Left" : "Right";
if (event.key === leftKey || event.key == rightKey || event.key === "Home" || event.key === "End") {
var focusableCommands = this._getFocusableCommandsInLogicalOrder();
var targetCommand;
if (focusableCommands.length) {
switch (event.key) {
case leftKey:
// Arrowing past the last command wraps back around to the first command.
var index = Math.max(-1, focusableCommands.focusedIndex - 1) + focusableCommands.length;
targetCommand = focusableCommands[index % focusableCommands.length].winControl.lastElementFocus;
break;
case rightKey:
// Arrowing previous to the first command wraps back around to the last command.
var index = focusableCommands.focusedIndex + 1 + focusableCommands.length;
targetCommand = focusableCommands[index % focusableCommands.length].winControl.firstElementFocus;
break;
case "Home":
var index = 0;
targetCommand = focusableCommands[index].winControl.firstElementFocus;
break;
case "End":
var index = focusableCommands.length - 1;
targetCommand = focusableCommands[index].winControl.lastElementFocus;
break;
}
}
if (targetCommand) {
targetCommand.focus();
// Prevent default so that Trident doesn't resolve the keydown event on the newly focused element.
event.preventDefault();
}
}
}
},
_getFocusableCommandsInLogicalOrder: function AppBar_getCommandsInLogicalOrder() {
// Function returns an array of all the contained AppBarCommands which are reachable by left/right arrows.
//
if (this.layout === appBarLayoutCommands) {
var selectionCommands = [],
globalCommands = [],
children = this._element.children,
globalCommandHasFocus = false,
focusedIndex = -1;
var categorizeCommand = function (element, isGlobalCommand, containsFocus) {
// Helper function to categorize the element by AppBarCommand's section property. The passed in element could be the
// AppBarCommand, or the element referenced by a content AppBarCommands's firstElementFocus/lastElementFocus property.
//
if (isGlobalCommand) {
globalCommands.push(element);
if (containsFocus) {
focusedIndex = globalCommands.length - 1;
globalCommandHasFocus = true;
}
} else {
selectionCommands.push(element);
if (containsFocus) {
focusedIndex = selectionCommands.length - 1;
}
}
}
// Separate commands into global and selection arrays. Find the current command with focus.
// Skip the first and last indices to avoid "firstDiv" and "finalDiv".
for (var i = 1, len = children.length; i < len - 1; i++) {
var element = children[i];
if (element && element.winControl) {
var containsFocus = element.contains(document.activeElement);
// With the inclusion of content type commands, it may be possible to tab to elements in AppBarCommands that are not reachable by arrow keys.
// Regardless, when an AppBarCommand contains the element with focus, we just include the whole command so that we can determine which
// Commands are adjacent to it when looking for the next focus destination.
if (element.winControl._isFocusable() || containsFocus) {
var isGlobalCommand = (element.winControl.section === "global");
categorizeCommand(element, isGlobalCommand, containsFocus);
}
}
}
var focusableCommands = selectionCommands.concat(globalCommands);
focusableCommands.focusedIndex = globalCommandHasFocus ? focusedIndex + selectionCommands.length : focusedIndex;
return focusableCommands;
}
},
_assignAnimations: function AppBar_assignAnimations() {
// Make sure the animations are correct for our current placement
if (this._placement === appBarPlacementTop || this._placement === appBarPlacementBottom) {
// Top or Bottom
this._currentAnimateIn = this._animateSlideIn;
this._currentAnimateOut = this._animateSlideOut;
} else {
// Default for in the middle of nowhere
this._currentAnimateIn = this._baseAnimateIn;
this._currentAnimateOut = this._baseAnimateOut;
}
},
// AppBar animations
_animateSlideIn: function AppBar_animateSlideIn() {
var where,
height = this._element.offsetHeight;
// Get top/bottoms
this._checkPosition();
// Get animation direction and clear other value
if (this._placement === appBarPlacementTop) {
// Top Bar
where = { top: "-" + height + "px", left: "0px" };
this._element.style.bottom = "auto";
} else {
// Bottom Bar
where = { top: height + "px", left: "0px" };
this._element.style.top = "auto";
}
this._element.style.opacity = 1;
this._element.style.visibility = "visible";
return WinJS.UI.Animation.showEdgeUI(this._element, where, { mechanism: "transition" });
},
_animateSlideOut: function AppBar_animateSlideOut() {
var where,
height = this._element.offsetHeight;
if (this._placement === appBarPlacementTop) {
// Top Bar
where = { top: "-" + height + "px", left: "0px" };
// Adjust for scrolling or soft keyboard positioning
this._element.style.top = (this._getTopOfVisualViewport()) + "px";
} else {
// Bottom Bar
where = { top: height + "px", left: "0px" };
// Adjust for scrolling or soft keyboard positioning
this._element.style.bottom = (this._getAdjustedBottom()) + "px";
}
return WinJS.UI.Animation.hideEdgeUI(this._element, where, { mechanism: "transition" });
},
_isABottomAppBarInTheProcessOfShowing: function AppBar_isABottomAppBarInTheProcessOfShowing() {
var appbars = document.querySelectorAll("." + appBarClass + "." + bottomClass);
for (var i = 0; i < appbars.length; i++) {
if (appbars[i].winAnimating === "showing") {
return true;
}
}
return false;
},
// Returns true if
// 1) This is a bottom appbar
// 2) No appbar has focus and a bottom appbar is not in the process of showing
// 3) What currently has focus is neither a bottom appbar nor a top appbar
// AND a bottom appbar is not in the process of showing.
// Otherwise Returns false
_shouldStealFocus: function AppBar_shouldStealFocus() {
var activeElementAppBar = thisWinUI.AppBar._isAppBarOrChild(document.activeElement);
if (this._element === activeElementAppBar) {
// This appbar already has focus and we don't want to move focus
// from where it currently is in this appbar.
return false;
}
if (this._placement === appBarPlacementBottom) {
// This is a bottom appbar
return true;
}
var isBottomAppBarShowing = this._isABottomAppBarInTheProcessOfShowing();
if (!activeElementAppBar) {
// Currently no appbar has focus.
// Return true if a bottom appbar is not in the process of showing.
return !isBottomAppBarShowing;
}
if (!activeElementAppBar.winControl) {
// This should not happen, but if it does we want to make sure
// that an AppBar ends up with focus.
return true;
}
if ((activeElementAppBar.winControl._placement !== appBarPlacementBottom)
&& (activeElementAppBar.winControl._placement !== appBarPlacementTop)
&& !isBottomAppBarShowing) {
// What currently has focus is neither a bottom appbar nor a top appbar
// -and-
// a bottom appbar is not in the process of showing.
return true;
}
return false
},
// Set focus to the passed in AppBar
_setFocusToAppBar: function AppBar_setFocusToAppBar() {
if (this._focusOnFirstFocusableElement()) {
// Prevent what is gaining focus from showing that it has focus,
// but only in the non-keyboard scenario.
if (!this._keyboardInvoked) {
thisWinUI._Overlay._addHideFocusClass(document.activeElement);
}
} else {
// No first element, set it to appbar itself
thisWinUI._Overlay._trySetActive(this._element);
}
},
_contentChanged: function AppBar_contentChanged() {
this._updateCommandsWidth();
this._scaleAppBar();
},
_scaleAppBar: function AppBar_scaleAppBar() {
// For commands layout AppBars only. If the total width of all AppBarCommands is greater than the
// width of the AppBar, add the win-reduced class to the AppBar element.
if (this.layout === appBarLayoutCommands) {
// Measure AppBar's contents width, AppBar offsetWidth and AppBar padding:
var widthOfVisibleContent = this._getCommandsWidth();
if (this._appBarTotalKnownWidth !== +this._appBarTotalKnownWidth) {
this._appBarTotalKnownWidth = this._scaleAppBarHelper();
}
if (widthOfVisibleContent <= this._appBarTotalKnownWidth) {
// Full size commands
WinJS.Utilities.removeClass(this._element, reducedClass);
}
else {
// Reduced size commands
WinJS.Utilities.addClass(this._element, reducedClass);
}
}
},
_scaleAppBarHelper: function AppBar_scaleAppBarHelper() {
// This exists as a single line function so that unit tests can
// overwrite it since they can't resize the WWA window.
return document.documentElement.clientWidth;
},
_updateCommandsWidth: function AppBar_updateCommandsWidth(commandSubSet) {
// Whenever Commands are hidden/shown in the Commands layout AppBar, this function is called
// to update the cached width measurement of all visible AppBarCommands in the AppBar.
if (this.layout === appBarLayoutCommands) {
var buttonsCount = 0;
var separatorsCount = 0;
var command;
var commands = commandSubSet;
this._widthOfAllCommands = 0;
if (!commands) {
// Crawl the AppBar's inner HTML for the commands.
commands = this._getVisibleCommands();
}
this._widthOfAllCommands = this._getCommandsWidth(commands);
}
},
_getCommandsWidth: function AppBar_getCommandsWidth(commandSubSet) {
if (!commandSubSet) {
// Return the cached width of all previously visible commands in the AppBar.
return this._widthOfAllCommands;
} else {
// Return the width of the specified subset.
var separatorsCount = 0;
var buttonsCount = 0;
var widthOfCommandSubSet = 0;
var command;
for (var i = 0, len = commandSubSet.length; i < len; i++) {
command = commandSubSet[i].winControl || commandSubSet[i];
if (command._type === typeSeparator) {
separatorsCount++
} else if (command._type !== typeContent) {
// button, toggle, and flyout types all have the same width.
buttonsCount++;
} else {
widthOfCommandSubSet += command._fullSizeWidth;
}
}
}
return widthOfCommandSubSet += (separatorsCount * separatorWidth) + (buttonsCount * buttonWidth);
},
_beginAnimateCommands: function AppBar_beginAnimateCommands(showCommands, hideCommands, otherVisibleCommands) {
// The parameters are 3 mutually exclusive arrays of win-command elements contained in this Overlay.
// 1) showCommands[]: All of the HIDDEN win-command elements that ARE scheduled to show.
// 2) hideCommands[]: All of the VISIBLE win-command elements that ARE scheduled to hide.
// 3) otherVisibleCommands[]: All VISIBLE win-command elements that ARE NOT scheduled to hide.
if (this.layout === appBarLayoutCommands) {
this._scaleCommandsAfterAnimations = false;
// Update our command counts now, to what they will be after we complete the animations.
var visibleCommandsAfterAnimations = otherVisibleCommands.concat(showCommands);
this._updateCommandsWidth(visibleCommandsAfterAnimations)
var changeInWidth = this._getCommandsWidth(showCommands) - this._getCommandsWidth(hideCommands);
if (changeInWidth > 0) {
// Width of contents is going to increase. If there won't be enough room to fit them all on a single row,
// reduce size of commands before the new content appears.
this._scaleAppBar();
} else if (changeInWidth < 0) {
// Width of contents is going to decrease. Once animations are complete, check if
// there is enough available space to make the remaining commands full size.
this._scaleCommandsAfterAnimations = true;
}
}
},
_endAnimateCommands: function AppBar_endAnimateCommands() {
if (this._scaleCommandsAfterAnimations) {
this._scaleAppBar();
}
},
_addCommand: function AppBar_addCommand(command) {
if (!command) {
throw new WinJS.ErrorFromName("WinJS.UI.AppBar.NullCommand", strings.nullCommand);
}
// See if it's a command already
if (!command._element) {
// Not a command, so assume it is options for the command's constructor.
command = new WinJS.UI.AppBarCommand(null, command);
}
// If we were attached somewhere else, detach us
if (command._element.parentElement) {
command._element.parentElement.removeChild(command._element);
}
// Reattach us
this._element.appendChild(command._element);
},
_measureContentCommands: function AppBar_measureContentCommands() {
// AppBar measures the width of content commands when they are first added
// and then caches that value to avoid additional layouts in the future.
// Can't measure unless We're in the document body
if (document.body.contains(this.element)) {
this._needToMeasure = false;
// Remove the reducedClass from AppBar to ensure fullsize measurements
var hadReducedClass = WinJS.Utilities.hasClass(this.element, reducedClass);
WinJS.Utilities.removeClass(this._element, reducedClass);
// Make sure AppBar and children have width dimensions.
var prevAppBarDisplay = this.element.style.display;
var prevCommandDisplay;
this.element.style.display = "";
var commandElements = this._element.children;
var element;
for (var i = 0, len = commandElements.length; i < len; i++) {
element = commandElements[i];
if (element.winControl && element.winControl._type === typeContent) {
// Make sure command has width dimensions before we measure.
prevCommandDisplay = element.style.display;
element.style.display = "";
element.winControl._fullSizeWidth = WinJS.Utilities.getTotalWidth(element) || 0;
element.style.display = prevCommandDisplay;
}
}
// Restore state to AppBar.
this.element.display = prevAppBarDisplay;
if (hadReducedClass) {
WinJS.Utilities.addClass(this._element, reducedClass);
}
}
},
// Performs any pending measurements on "content" type AppBarCommands and scales the AppBar to fit all AppBarCommand types accordingly.
_layoutCommands: function AppBar_layoutCommands() {
if (this._needToMeasure && this.layout === appBarLayoutCommands) {
this._measureContentCommands();
this._contentChanged();
}
},
// Get the top of the top appbars, this is always 0 because appbar uses
// -ms-device-fixed positioning.
_getTopOfVisualViewport: function AppBar_getTopOfVisualViewPort() {
return 0;
},
// Get the bottom of the bottom appbars, Bottom is just 0, if there's no IHM.
// When the IHM appears, the default behavior is to resize the view. If a resize
// happens, we can rely on -ms-device-fixed positioning and leave the bottom
// at 0. However if resize doesn't happen, then the keyboard obscures the appbar
// and we will need to adjust the bottom of the appbar by distance of the keyboard.
_getAdjustedBottom: function AppBar_getAdjustedBottom() {
// Need the distance the IHM moved as well.
return thisWinUI._Overlay._keyboardInfo._visibleDocBottomOffset;
},
_showingKeyboard: function AppBar_showingKeyboard(event) {
// Remember keyboard showing state.
this._keyboardObscured = false;
this._keyboardHiding = false;
// If we're already moved, then ignore the whole thing
if (thisWinUI._Overlay._keyboardInfo._visible && this._alreadyInPlace()) {
return;
}
this._keyboardShowing = true;
// If focus is in the appbar, don't cause scrolling.
if (!this.hidden && this._element.contains(document.activeElement)) {
event.ensuredFocusedElementInView = true;
}
// Check if appbar moves or is obscured
if (!this.hidden && this._placement !== appBarPlacementTop && thisWinUI._Overlay._isFlyoutVisible()) {
// Remember that we're obscured
this._keyboardObscured = true;
} else {
// If not obscured, tag as showing and set timeout to restore us.
this._scrollHappened = false;
}
// Also set timeout regardless, so we can clean up our _keyboardShowing flag.
var that = this;
setTimeout(function (e) { that._checkKeyboardTimer(e); }, thisWinUI._Overlay._keyboardInfo._animationShowLength + thisWinUI._Overlay._scrollTimeout);
},
_hidingKeyboard: function AppBar_hidingKeyboard(event) {
// We won't be obscured
this._keyboardObscured = false;
this._keyboardShowing = false;
this._keyboardHiding = true;
// We'll either just reveal the current space or resize the window
if (!thisWinUI._Overlay._keyboardInfo._isResized) {
// If we're visible or only fake hiding under keyboard, or already animating,
// then snap us to our final position.
if (!this.hidden || this._fakeHide || this._animating) {
// Not resized, update our final position immediately
this._checkScrollPosition();
this._element.style.display = "";
this._fakeHide = false;
}
this._keyboardHiding = false;
}
// Else resize should clear keyboardHiding.
},
_resize: function AppBar_resize(event) {
// If we're hidden by the keyboard, then hide bottom appbar so it doesn't pop up twice when it scrolls
if (this._keyboardShowing) {
// Top is allowed to scroll off the top, but we don't want bottom to peek up when
// scrolled into view since we'll show it ourselves and don't want a stutter effect.
if (!this.hidden) {
if (this._placement !== appBarPlacementTop && !this._keyboardObscured) {
// If viewport doesn't match window, need to vanish momentarily so it doesn't scroll into view,
// however we don't want to toggle the visibility="hidden" hidden flag.
this._element.style.display = "none";
}
}
// else if we're top we stay, and if there's a flyout, stay obscured by the keyboard.
} else if (this._keyboardHiding) {
this._keyboardHiding = false;
if (!this.hidden || this._animating) {
// Snap to final position
this._checkScrollPosition();
this._element.style.display = "";
this._fakeHide = false;
}
}
// Check for horizontal window resizes.
this._appBarTotalKnownWidth = null;
if (!this.hidden) {
this._scaleAppBar();
}
},
_checkKeyboardTimer: function AppBar_checkKeyboardTimer() {
if (!this._scrollHappened) {
this._mayEdgeBackIn();
}
},
_manipulationChanged: function AppBar_manipulationChanged(event) {
// See if we're at the not manipulating state, and we had a scroll happen,
// which is implicitly after the keyboard animated.
if (event.currentState === 0 && this._scrollHappened) {
this._mayEdgeBackIn();
}
},
_mayEdgeBackIn: function AppBar_mayEdgeBackIn(event) {
// May need to react to IHM being resized event
if (this._keyboardShowing) {
// If not top appbar or viewport isn't still at top, then need to show again
this._keyboardShowing = false;
// If obscured (flyout showing), don't change.
// If hidden, may be because _fakeHide was set in _resize.
// If bottom we have to move, or if top scrolled off screen.
if (!this._keyboardObscured && (!this.hidden || this._fakeHide) &&
(this._placement !== appBarPlacementTop || thisWinUI._Overlay._keyboardInfo._visibleDocTop !== 0)) {
this._doNotFocus = true;
this._fakeHide = true;
this._show();
} else {
// Ensure any animation dropped during the showing keyboard are caught up.
this._checkDoNext();
}
}
this._scrollHappened = false;
},
// _checkPosition repositions the AppBar when the soft keyboard shows up
_checkPosition: function AppBar_checkPosition() {
// Bottom's the only one needing movement
if (this._placement === appBarPlacementBottom) {
this._element.style.bottom = this._getAdjustedBottom() + "px";
} else if (this._placement === appBarPlacementTop) {
this._element.style.top = this._getTopOfVisualViewport() + "px";
}
// else we don't touch custom positions
},
_checkScrollPosition: function AppBar_checkScrollPosition(event) {
// If keyboard's animating, then remember we may come in
if (this._keyboardShowing) {
// Tag that it's OK to edge back in.
this._scrollHappened = true;
return;
}
// We only need to update if we're visible
if (!this.hidden || this._animating) {
this._checkPosition();
// Ensure any animation dropped during the showing keyboard are caught up.
this._checkDoNext();
}
},
_alreadyInPlace: function AppBar_alreadyInPlace() {
// See if we're already where we're supposed to be.
if (this._placement === appBarPlacementBottom) {
if (parseInt(this._element.style.bottom) === this._getAdjustedBottom()) {
return true;
}
} else if (this._placement === appBarPlacementTop) {
if (parseInt(this._element.style.top) === this._getTopOfVisualViewport()) {
return true;
}
}
// else we don't understand custom positioning
return false;
},
// If there is a visible non-sticky AppBar then it sets the firstDiv tabIndex to
// the minimum tabIndex found in the AppBars and finalDiv to the max found.
// Otherwise sets their tabIndex to -1 so they are not tab stops.
_updateFirstAndFinalDiv: function AppBar_updateFirstAndFinalDiv() {
var appBarFirstDiv = this._element.querySelectorAll("." + firstDivClass);
appBarFirstDiv = appBarFirstDiv.length >= 1 ? appBarFirstDiv[0] : null;
var appBarFinalDiv = this._element.querySelectorAll("." + finalDivClass);
appBarFinalDiv = appBarFinalDiv.length >= 1 ? appBarFinalDiv[0] : null;
// Remove the firstDiv & finalDiv if they are not at the appropriate locations
if (appBarFirstDiv && (this._element.children[0] != appBarFirstDiv)) {
appBarFirstDiv.parentNode.removeChild(appBarFirstDiv);
appBarFirstDiv = null;
}
if (appBarFinalDiv && (this._element.children[this._element.children.length - 1] != appBarFinalDiv)) {
appBarFinalDiv.parentNode.removeChild(appBarFinalDiv);
appBarFinalDiv = null;
}
// Create and add the firstDiv & finalDiv if they don't already exist
if (!appBarFirstDiv) {
// Add a firstDiv that will be the first child of the appBar.
// On focus set focus to the previous appBar.
// The div should only be focusable if there are visible non-sticky AppBars.
appBarFirstDiv = document.createElement("div");
// display: inline is needed so that the div doesn't take up space and cause the page to scroll on focus
appBarFirstDiv.style.display = "inline";
appBarFirstDiv.className = firstDivClass;
appBarFirstDiv.tabIndex = -1;
appBarFirstDiv.setAttribute("aria-hidden", "true");
appBarFirstDiv.addEventListener("focus", _setFocusToPreviousAppBar, true);
this._element.insertAdjacentElement("AfterBegin", appBarFirstDiv);
}
if (!appBarFinalDiv) {
// Add a finalDiv that will be the last child of the appBar.
// On focus set focus to the next appBar.
// The div should only be focusable if there are visible non-sticky AppBars.
appBarFinalDiv = document.createElement("div");
// display: inline is needed so that the div doesn't take up space and cause the page to scroll on focus
appBarFinalDiv.style.display = "inline";
appBarFinalDiv.className = finalDivClass;
appBarFinalDiv.tabIndex = -1;
appBarFinalDiv.setAttribute("aria-hidden", "true");
appBarFinalDiv.addEventListener("focus", _setFocusToNextAppBar, true);
this._element.appendChild(appBarFinalDiv);
}
// Update the tabIndex of the firstDiv & finalDiv
if (_isThereVisibleNonStickyBar()) {
var elms = this._element.getElementsByTagName("*");
if (appBarFirstDiv) {
appBarFirstDiv.tabIndex = WinJS.Utilities._getLowestTabIndexInList(elms);
}
if (appBarFinalDiv) {
appBarFinalDiv.tabIndex = WinJS.Utilities._getHighestTabIndexInList(elms);
}
} else {
if (appBarFirstDiv) {
appBarFirstDiv.tabIndex = -1;
}
if (appBarFinalDiv) {
appBarFinalDiv.tabIndex = -1;
}
}
},
_writeProfilerMark: function AppBar_writeProfilerMark(text) {
msWriteProfilerMark("WinJS.UI.AppBar:" + this._id + ":" + text);
}
});
// Statics
AppBar._ElementWithFocusPreviousToAppBar = null;
// Returns appbar element (or CED/sentinal) if the element or what had focus before the element (if a Flyout) is either:
// 1) an AppBar,
// 2) OR in the subtree of an AppBar,
// 3) OR an AppBar click eating div.
// Returns null otherwise.
AppBar._isAppBarOrChild = function (element) {
// If it's null, we can't do this
if (!element) {
return null;
}
// click eating divs and sentinals should not have children
if (WinJS.Utilities.hasClass(element, thisWinUI._Overlay._clickEatingAppBarClass) ||
WinJS.Utilities.hasClass(element, thisWinUI._Overlay._clickEatingFlyoutClass) ||
WinJS.Utilities.hasClass(element, firstDivClass) ||
WinJS.Utilities.hasClass(element, finalDivClass)) {
return element;
}
while (element && element !== document) {
if (WinJS.Utilities.hasClass(element, appBarClass)) {
return element;
}
if (WinJS.Utilities.hasClass(element, "win-flyout")
&& element != element.winControl._previousFocus) {
var flyoutControl = element.winControl;
// If _previousFocus was in a light dismissable AppBar, then this Flyout is considered of an extension of it and that AppBar will not close.
// Hook up a 'focusout' listener to this Flyout element to make sure that light dismiss AppBars close if focus moves anywhere other than back to an AppBar.
var appBarElement = thisWinUI.AppBar._isAppBarOrChild(flyoutControl._previousFocus);
if (appBarElement) {
flyoutControl.element.addEventListener('focusout', function focusOut(event) {
// Hides any open AppBars if the new activeElement is not in an AppBar.
_hideIfAllAppBarsLostFocus();
flyoutControl.element.removeEventListener('focusout', focusOut, false);
}, false);
}
return appBarElement;
}
element = element.parentNode;
}
return null;
};
// Returns true if the element or what had focus before the element (if a Flyout) is either:
// 1) the appBar or subtree
// 2) OR in a flyout spawned by the appBar
// Returns false otherwise.
AppBar._isWithinAppBarOrChild = function (element, appBar) {
if (!element || !appBar) {
return false;
}
if (appBar.contains(element)) {
return true;
}
var flyout = thisWinUI._Overlay._getParentControlUsingClassName(element, "win-flyout");
return (flyout && appBar.contains(flyout._previousFocus));
};
// Overlay class calls this for global light dismiss events
AppBar._hideLightDismissAppBars = function (event, keyboardInvoked) {
var elements = document.querySelectorAll("." + appBarClass);
var len = elements.length;
var AppBars = [];
for (var i = 0; i < len; i++) {
var AppBar = elements[i].winControl;
if (AppBar && !AppBar.sticky && !AppBar.hidden) {
AppBars.push(AppBar);
}
}
_hideAllBars(AppBars, keyboardInvoked);
};
var appBarSynchronizationPromise = WinJS.Promise.as();
// Callback for AppBar Edgy Event Command
AppBar._toggleAppBarEdgy = function (keyboardInvoked) {
var bars = _getDynamicBarsForEdgy();
// If they're all visible hide them, otherwise show them all
if (bars._visible && !bars._hidden) {
appBarSynchronizationPromise = appBarSynchronizationPromise.then(function () {
return _hideAllBars(bars, keyboardInvoked);
});
return "hiding";
} else {
appBarSynchronizationPromise = appBarSynchronizationPromise.then(function () {
return _showAllBars(bars, keyboardInvoked);
});
return "showing";
}
};
return AppBar;
})
});
})(WinJS);
/// appbar,Flyout,Flyouts,Statics
(function flyoutInit(WinJS) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Displays lightweight UI that is either informational, or requires user interaction.
/// Unlike a dialog, a Flyout can be light dismissed by clicking or tapping off of it.
///
///
///
/// Flyout
///
///
/// ]]>
/// Raised just before showing a flyout.
/// Raised immediately after a flyout is fully shown.
/// Raised just before hiding a flyout.
/// Raised immediately after a flyout is fully hidden.
/// The Flyout control itself.
///
///
///
Flyout: WinJS.Namespace._lazy(function () {
var thisWinUI = WinJS.UI;
// Class Names
var appBarCommandClass = "win-command";
var flyoutClass = "win-flyout";
var flyoutLightClass = "win-ui-light";
var menuClass = "win-menu";
var scrollsClass = "win-scrolls";
var finalDivClass = "win-finaldiv";
var firstDivClass = "win-firstdiv";
function getDimension(element, property) {
return parseFloat(element, window.getComputedStyle(element, null)[property]);
}
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/flyoutAriaLabel").value; },
get noAnchor() { return WinJS.Resources._getWinJSString("ui/noAnchor").value; },
get badPlacement() { return WinJS.Resources._getWinJSString("ui/badPlacement").value; },
get badAlignment() { return WinJS.Resources._getWinJSString("ui/badAlignment").value; }
};
var Flyout = WinJS.Class.derive(WinJS.UI._Overlay, function Flyout_ctor(element, options) {
///
///
/// Creates a new Flyout control.
///
///
/// The DOM element that hosts the control.
///
///
/// The set of properties and values to apply to the new Flyout.
///
/// The new Flyout control.
///
///
// Simplify checking later
options = options || {};
// Make sure there's an input element
this._element = element || document.createElement("div");
this._id = this._element.id || this._element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
this._baseFlyoutConstructor(this._element, options);
var _elms = this._element.getElementsByTagName("*");
var firstDiv = this._addFirstDiv();
firstDiv.tabIndex = WinJS.Utilities._getLowestTabIndexInList(_elms);
var finalDiv = this._addFinalDiv();
finalDiv.tabIndex = WinJS.Utilities._getHighestTabIndexInList(_elms);
// Handle "esc" & "tab" key presses
this._element.addEventListener("keydown", this._handleKeyDown, true);
this._writeProfilerMark("constructor,StopTM");
return this;
}, {
_lastMaxHeight: null,
_baseFlyoutConstructor: function Flyout_baseFlyoutContstructor(element, options) {
// Flyout constructor
// We have some options with defaults
this._placement = "auto";
this._alignment = "center";
// Call the base overlay constructor helper
this._baseOverlayConstructor(element, options);
// Make a click eating div
thisWinUI._Overlay._createClickEatingDivFlyout();
// Start flyouts hidden
this._element.style.visibilty = "hidden";
this._element.style.display = "none";
// Attach our css class
WinJS.Utilities.addClass(this._element, flyoutClass);
WinJS.Utilities.addClass(this._element, flyoutLightClass);
// Make sure we have an ARIA role
var role = this._element.getAttribute("role");
if (role === null || role === "" || role === undefined) {
if (WinJS.Utilities.hasClass(this._element, menuClass)) {
this._element.setAttribute("role", "menu");
} else {
this._element.setAttribute("role", "dialog");
}
}
var label = this._element.getAttribute("aria-label");
if (label === null || label === "" || label === undefined) {
this._element.setAttribute("aria-label", strings.ariaLabel);
}
// Base animation is popIn, but our flyout has different arguments
this._currentAnimateIn = this._flyoutAnimateIn;
this._currentAnimateOut = this._flyoutAnimateOut;
// Make sure _Overlay event handlers are hooked up
this._addOverlayEventHandlers(true);
},
///
/// Gets or sets the Flyout control's anchor. The anchor element is the HTML element which the Flyout originates from and is positioned relative to.
/// (This setting can be overridden when you call the show method.)
///
///
anchor: {
get: function () {
return this._anchor;
},
set: function (value) {
this._anchor = value;
}
},
///
/// Gets or sets the default placement of this Flyout. (This setting can be overridden when you call the show method.)
///
///
placement: {
get: function () {
return this._placement;
},
set: function (value) {
if (value !== "top" && value !== "bottom" && value !== "left" && value !== "right" && value !== "auto") {
// Not a legal placement value
throw new WinJS.ErrorFromName("WinJS.UI.Flyout.BadPlacement", strings.badPlacement);
}
this._placement = value;
}
},
///
/// Gets or sets the default alignment for this Flyout. (This setting can be overridden when you call the show method.)
///
///
alignment: {
get: function () {
return this._alignment;
},
set: function (value) {
if (value !== "right" && value !== "left" && value !== "center") {
// Not a legal alignment value
throw new WinJS.ErrorFromName("WinJS.UI.Flyout.BadAlignment", strings.badAlignment);
}
this._alignment = value;
}
},
_dispose: function Flyout_dispose() {
WinJS.Utilities.disposeSubTree(this.element);
this._hide();
this.anchor = null;
},
show: function (anchor, placement, alignment) {
///
///
/// Shows the Flyout, if hidden, regardless of other states.
///
///
/// The DOM element, or ID of a DOM element to anchor the Flyout, overriding the anchor property for this time only.
///
///
/// The placement of the Flyout to the anchor: 'auto' (default), 'top', 'bottom', 'left', or 'right'. This parameter overrides the placement property for this show only.
///
///
/// For 'top' or 'bottom' placement, the alignment of the Flyout to the anchor's edge: 'center' (default), 'left', or 'right'.
/// This parameter overrides the alignment property for this show only.
///
///
///
this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
// Just call private version to make appbar flags happy
this._show(anchor, placement, alignment);
},
_show: function Flyout_show(anchor, placement, alignment) {
this._baseFlyoutShow(anchor, placement, alignment);
},
hide: function () {
///
///
/// Hides the Flyout, if visible, regardless of other states.
///
///
///
// Just wrap the private one, turning off keyboard invoked flag
this._writeProfilerMark("hide,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndHide().
this._keyboardInvoked = false;
this._hide();
},
_hide: function Flyout_hide() {
if (this._baseHide()) {
// Return focus if this or the flyout CED has focus
var active = document.activeElement;
if (this._previousFocus
&& active
&& (this._element.contains(active)
|| WinJS.Utilities.hasClass(active, thisWinUI._Overlay._clickEatingFlyoutClass))
&& this._previousFocus.focus !== undefined) {
// _isAppBarOrChild may return a CED or sentinal
var appBar = thisWinUI.AppBar._isAppBarOrChild(this._previousFocus);
if (!appBar || (appBar.winControl && !appBar.winControl.hidden && !appBar.winAnimating)) {
// Don't move focus back to a appBar that is hidden
// We cannot rely on element.style.visibility because it will be visible while animating
var role = this._previousFocus.getAttribute("role");
var fHideRole = thisWinUI._Overlay._keyboardInfo._visible && !this._keyboardWasUp;
if (fHideRole) {
// Convince IHM to dismiss because it only came up after the flyout was up.
// Change aria role and back to get IHM to dismiss.
this._previousFocus.setAttribute("role", "");
}
if (this._keyboardInvoked) {
this._previousFocus.focus();
} else {
thisWinUI._Overlay._trySetActive(this._previousFocus);
}
active = document.activeElement;
if (fHideRole) {
// Restore the role so that css is applied correctly
var previousFocus = this._previousFocus;
if (previousFocus) {
setImmediate(function () {
previousFocus.setAttribute("role", role);
});
}
}
}
// If the anchor gained focus we want to hide the focus in the non-keyboarding scenario
if (!this._keyboardInvoked && (this._previousFocus === active) && appBar && active) {
thisWinUI._Overlay._addHideFocusClass(active);
}
}
this._previousFocus = null;
// Need click-eating div to be hidden if there are no other visible flyouts
if (!this._isThereVisibleFlyout()) {
thisWinUI._Overlay._hideClickEatingDivFlyout();
}
}
},
_baseFlyoutShow: function Flyout_baseFlyoutShow(anchor, placement, alignment) {
// Don't do anything if disabled
if (this.disabled) {
return;
}
// Pick up defaults
if (!anchor) {
anchor = this._anchor;
}
if (!placement) {
placement = this._placement;
}
if (!alignment) {
alignment = this._alignment;
}
// Dereference the anchor if necessary
if (typeof anchor === "string") {
anchor = document.getElementById(anchor);
} else if (anchor && anchor.element) {
anchor = anchor.element;
}
// We expect an anchor
if (!anchor) {
// If we have _nextLeft, etc., then we were continuing an old animation, so that's OK
if (!this._retryLast) {
throw new WinJS.ErrorFromName("WinJS.UI.Flyout.NoAnchor", strings.noAnchor);
}
// Last call was incomplete, so use the previous _current values.
this._retryLast = null;
} else {
// Remember the anchor so that if we lose focus we can go back
this._currentAnchor = anchor;
// Remember current values
this._currentPlacement = placement;
this._currentAlignment = alignment;
}
// Need click-eating div to be visible, no matter what
if (!this._sticky) {
thisWinUI._Overlay._showClickEatingDivFlyout();
}
// If we're animating (eg baseShow is going to fail), then don't mess up our current state.
// Queue us up to wait for current animation to finish first.
if (this._element.winAnimating) {
this._doNext = "show";
this._retryLast = true;
return;
}
// We call our base _baseShow to handle the actual animation
if (this._baseShow()) {
// (_baseShow shouldn't ever fail because we tested winAnimating above).
if (!WinJS.Utilities.hasClass(this.element, "win-menu")) {
// Verify that the firstDiv is in the correct location.
// Move it to the correct location or add it if not.
var _elms = this._element.getElementsByTagName("*");
var firstDiv = this.element.querySelectorAll(".win-first");
if (this.element.children.length && !WinJS.Utilities.hasClass(this.element.children[0], firstDivClass)) {
if (firstDiv && firstDiv.length > 0) {
firstDiv.item(0).parentNode.removeChild(firstDiv.item(0));
}
firstDiv = this._addFirstDiv();
}
firstDiv.tabIndex = WinJS.Utilities._getLowestTabIndexInList(_elms);
// Verify that the finalDiv is in the correct location.
// Move it to the correct location or add it if not.
var finalDiv = this.element.querySelectorAll(".win-final");
if (!WinJS.Utilities.hasClass(this.element.children[this.element.children.length - 1], finalDivClass)) {
if (finalDiv && finalDiv.length > 0) {
finalDiv.item(0).parentNode.removeChild(finalDiv.item(0));
}
finalDiv = this._addFinalDiv();
}
finalDiv.tabIndex = WinJS.Utilities._getHighestTabIndexInList(_elms);
}
// Hide all other flyouts
this._hideAllOtherFlyouts(this);
// Store what had focus before showing the Flyout.
// This must happen after we hide all other flyouts so that we store the correct element.
this._previousFocus = document.activeElement;
}
},
_endShow: function Flyout_endShow() {
// Remember if the IHM was up since we may need to hide it when the flyout hides.
// This check needs to happen after the IHM has a chance to hide itself after we force hide
// all other visible Flyouts.
this._keyboardWasUp = thisWinUI._Overlay._keyboardInfo._visible;
if (!WinJS.Utilities.hasClass(this.element, "win-menu")) {
// Put focus on the first child in the Flyout
this._focusOnFirstFocusableElementOrThis();
// Prevent what is gaining focus from showing that it has focus
thisWinUI._Overlay._addHideFocusClass(document.activeElement);
} else {
// Make sure the menu has focus, but don't show a focus rect
thisWinUI._Overlay._trySetActive(this._element);
}
},
// Find our new flyout position.
_findPosition: function Flyout_findPosition() {
this._nextHeight = null;
this._keyboardMovedUs = false;
this._hasScrolls = false;
this._keyboardSquishedUs = 0;
// Make sure menu toggles behave
if (this._checkToggle) {
this._checkToggle();
}
// Update margins for this alignment and remove old scrolling
this._updateAdjustments(this._currentAlignment);
// Set up the new position, and prep the offset for showPopup
this._getTopLeft();
// Panning top offset is calculated top
this._scrollTop = this._nextTop;
// Adjust position
if (this._nextTop < 0) {
// Need to attach to bottom
this._element.style.bottom = "0px";
this._element.style.top = "auto";
} else {
// Normal, attach to top
this._element.style.top = this._nextTop + "px";
this._element.style.bottom = "auto";
}
if (this._nextLeft < 0) {
// Overran right, attach to right
this._element.style.right = "0px";
this._element.style.left = "auto";
} else {
// Normal, attach to left
this._element.style.left = this._nextLeft + "px";
this._element.style.right = "auto";
}
// Adjust height/scrollbar
if (this._nextHeight !== null) {
WinJS.Utilities.addClass(this._element, scrollsClass);
this._lastMaxHeight = this._element.style.maxHeight;
this._element.style.maxHeight = this._nextHeight + "px";
this._nextBottom = this._nextTop + this._nextHeight;
this._hasScrolls = true;
}
// May need to adjust if the IHM is showing.
if (thisWinUI._Overlay._keyboardInfo._visible) {
// Use keyboard logic
this._checkKeyboardFit();
if (this._keyboardMovedUs) {
this._adjustForKeyboard();
}
}
},
// This determines our positioning. We have 5 modes, the 1st four are explicit, the last is automatic:
// * top - position explicitly on the top of the anchor, shrinking and adding scrollbar as needed.
// * bottom - position explicitly below the anchor, shrinking and adding scrollbar as needed.
// * left - position left of the anchor, shrinking and adding a vertical scrollbar as needed.
// * right - position right of the anchor, shrinking and adding a vertical scroolbar as needed.
// * auto - Automatic placement.
// Auto tests the height of the anchor and the flyout. For consistency in orientation, we imagine
// that the anchor is placed in the vertical center of the display. If the flyout would fit above
// that centered anchor, then we will place the flyout vertically in relation to the anchor, otherwise
// placement will be horizontal.
// Vertical auto placement will be positioned on top of the anchor if room, otherwise below the anchor.
// - this is because touch users would be more likely to obscure flyouts below the anchor.
// Horizontal auto placement will be positioned to the left of the anchor if room, otherwise to the right.
// - this is because right handed users would be more likely to obscure a flyout on the right of the anchor.
// Auto placement will add a vertical scrollbar if necessary.
_getTopLeft: function Flyout_getTopLeft() {
var anchorRawRectangle = this._currentAnchor.getBoundingClientRect(),
flyout = {},
anchor = {};
// Adjust for the anchor's margins.
anchor.top = anchorRawRectangle.top;
anchor.bottom = anchorRawRectangle.bottom;
anchor.left = anchorRawRectangle.left;
anchor.right = anchorRawRectangle.right;
anchor.height = anchor.bottom - anchor.top;
anchor.width = anchor.right - anchor.left;
// Get our flyout and margins, note that getDimension calls
// window.getComputedStyle, which ensures layout is updated.
flyout.marginTop = getDimension(this._element, "marginTop");
flyout.marginBottom = getDimension(this._element, "marginBottom");
flyout.marginLeft = getDimension(this._element, "marginLeft");
flyout.marginRight = getDimension(this._element, "marginRight");
flyout.width = WinJS.Utilities.getTotalWidth(this._element);
flyout.height = WinJS.Utilities.getTotalHeight(this._element);
flyout.innerWidth = WinJS.Utilities.getContentWidth(this._element);
flyout.innerHeight = WinJS.Utilities.getContentHeight(this._element);
this._nextMarginPadding = (flyout.height - flyout.innerHeight);
// Check fit for requested this._currentPlacement, doing fallback if necessary
switch (this._currentPlacement) {
case "top":
if (!this._fitTop(anchor, flyout)) {
// Didn't fit, needs scrollbar
this._nextTop = 0;
this._nextHeight = anchor.top - this._nextMarginPadding;
}
this._centerHorizontally(anchor, flyout, this._currentAlignment);
break;
case "bottom":
if (!this._fitBottom(anchor, flyout)) {
// Didn't fit, needs scrollbar
this._nextTop = -1;
this._nextHeight = thisWinUI._Overlay._keyboardInfo._visibleDocHeight - anchor.bottom - this._nextMarginPadding;
}
this._centerHorizontally(anchor, flyout, this._currentAlignment);
break;
case "left":
if (!this._fitLeft(anchor, flyout)) {
// Didn't fit, just shove it to edge
this._nextLeft = 0;
}
this._centerVertically(anchor, flyout);
break;
case "right":
if (!this._fitRight(anchor, flyout)) {
// Didn't fit,just shove it to edge
this._nextLeft = -1;
}
this._centerVertically(anchor, flyout);
break;
case "auto":
// Auto, if the anchor was in the vertical center of the display would we fit above it?
if (this._sometimesFitsAbove(anchor, flyout)) {
// It will fit above or below the anchor
if (!this._fitTop(anchor, flyout)) {
// Didn't fit above (preferred), so go below.
this._fitBottom(anchor, flyout);
}
this._centerHorizontally(anchor, flyout, this._currentAlignment);
} else {
// Won't fit above or below, try a side
if (!this._fitLeft(anchor, flyout) &&
!this._fitRight(anchor, flyout)) {
// Didn't fit left or right either, is top or bottom bigger?
if (this._topHasMoreRoom(anchor)) {
// Top, won't fit, needs scrollbar
this._nextTop = 0;
this._nextHeight = anchor.top - this._nextMarginPadding;
} else {
// Bottom, won't fit, needs scrollbar
this._nextTop = -1;
this._nextHeight = thisWinUI._Overlay._keyboardInfo._visibleDocHeight - anchor.bottom - this._nextMarginPadding;
}
this._centerHorizontally(anchor, flyout, this._currentAlignment);
} else {
this._centerVertically(anchor, flyout);
}
}
break;
default:
// Not a legal this._currentPlacement value
throw new WinJS.ErrorFromName("WinJS.UI.Flyout.BadPlacement", strings.badPlacement);
}
// Remember "bottom" in case we need to consider keyboard later, only tested for top-pinned bars
this._nextBottom = this._nextTop + flyout.height;
},
// If the anchor is centered vertically, would the flyout fit above it?
_sometimesFitsAbove: function Flyout_sometimesFitsAbove(anchor, flyout) {
return ((thisWinUI._Overlay._keyboardInfo._visibleDocHeight - anchor.height) / 2) >= flyout.height;
},
_topHasMoreRoom: function Flyout_topHasMoreRoom(anchor) {
return anchor.top > thisWinUI._Overlay._keyboardInfo._visibleDocHeight - anchor.bottom;
},
// See if we can fit in various places, fitting in the main view,
// ignoring viewport changes, like for the IHM.
_fitTop: function Flyout_fitTop(anchor, flyout) {
this._nextTop = anchor.top - flyout.height;
this._nextAnimOffset = { top: "50px", left: "0px", keyframe: "WinJS-showFlyoutTop" };
return (this._nextTop >= 0 &&
this._nextTop + flyout.height <= thisWinUI._Overlay._keyboardInfo._visibleDocBottom);
},
_fitBottom: function Flyout_fitBottom(anchor, flyout) {
this._nextTop = anchor.bottom;
this._nextAnimOffset = { top: "-50px", left: "0px", keyframe: "WinJS-showFlyoutBottom" };
return (this._nextTop >= 0 &&
this._nextTop + flyout.height <= thisWinUI._Overlay._keyboardInfo._visibleDocBottom);
},
_fitLeft: function Flyout_fitLeft(anchor, flyout) {
this._nextLeft = anchor.left - flyout.width;
this._nextAnimOffset = { top: "0px", left: "50px", keyframe: "WinJS-showFlyoutLeft" };
return (this._nextLeft >= 0 && this._nextLeft + flyout.width <= thisWinUI._Overlay._keyboardInfo._visualViewportWidth);
},
_fitRight: function Flyout_fitRight(anchor, flyout) {
this._nextLeft = anchor.right;
this._nextAnimOffset = { top: "0px", left: "-50px", keyframe: "WinJS-showFlyoutRight" };
return (this._nextLeft >= 0 && this._nextLeft + flyout.width <= thisWinUI._Overlay._keyboardInfo._visualViewportWidth);
},
_centerVertically: function Flyout_centerVertically(anchor, flyout) {
this._nextTop = anchor.top + anchor.height / 2 - flyout.height / 2;
if (this._nextTop < 0) {
this._nextTop = 0;
} else if (this._nextTop + flyout.height >= thisWinUI._Overlay._keyboardInfo._visibleDocBottom) {
// Flag to put on bottom
this._nextTop = -1;
}
},
_centerHorizontally: function Flyout_centerHorizontally(anchor, flyout, alignment) {
if (alignment === "center") {
this._nextLeft = anchor.left + anchor.width / 2 - flyout.width / 2;
} else if (alignment === "left") {
this._nextLeft = anchor.left;
} else if (alignment === "right") {
this._nextLeft = anchor.right - flyout.width;
} else {
throw new WinJS.ErrorFromName("WinJS.UI.Flyout.BadAlignment", strings.badAlignment);
}
if (this._nextLeft < 0) {
this._nextLeft = 0;
} else if (this._nextLeft + flyout.width >= document.documentElement.clientWidth) {
// flag to put on right
this._nextLeft = -1;
}
},
_updateAdjustments: function Flyout_updateAdjustments(alignment) {
// Move to 0,0 in case it is off screen, so that it lays out at a reasonable size
this._element.style.top = "0px";
this._element.style.bottom = "auto";
this._element.style.left = "0px";
this._element.style.right = "auto";
// Scrolling may not be necessary
WinJS.Utilities.removeClass(this._element, scrollsClass);
if (this._lastMaxHeight !== null) {
this._element.style.maxHeight = this._lastMaxHeight;
this._lastMaxHeight = null;
}
// Alignment
if (alignment === "center") {
WinJS.Utilities.removeClass(this._element, "win-leftalign");
WinJS.Utilities.removeClass(this._element, "win-rightalign");
} else if (alignment === "left") {
WinJS.Utilities.addClass(this._element, "win-leftalign");
WinJS.Utilities.removeClass(this._element, "win-rightalign");
} else if (alignment === "right") {
WinJS.Utilities.addClass(this._element, "win-rightalign");
WinJS.Utilities.removeClass(this._element, "win-leftalign");
}
},
_showingKeyboard: function Flyout_showingKeyboard(event) {
if (this.hidden) {
return;
}
// The only way that we can be showing a keyboard when a flyout is up is because the input was
// in the flyout itself, in which case we'll be moving ourselves. There is no practical way
// for the application to override this as the focused element is in our flyout.
event.ensuredFocusedElementInView = true;
// See if the keyboard is going to force us to move
this._checkKeyboardFit();
if (this._keyboardMovedUs) {
// Pop out immediately, then move to new spot
this._element.style.opacity = 0;
var that = this;
setTimeout(function () { that._adjustForKeyboard(); that._baseAnimateIn(); }, thisWinUI._Overlay._keyboardInfo._animationShowLength);
}
},
_resize: function Flyout_resize(event) {
// If hidden and not busy animating, then nothing to do
if (this.hidden && !this._animating) {
return;
}
// This should only happen if the IHM is dismissing,
// the only other way is for viewstate changes, which
// would dismiss any flyout.
if (this._keyboardHiding) {
// Hiding keyboard, update our position, giving the anchor a chance to update first.
var that = this;
setImmediate(function () { that._findPosition(); });
this._keyboardHiding = false;
}
},
_checkKeyboardFit: function Flyout_checkKeyboardFit() {
// Check for moving to fit keyboard:
// - Too Tall, above top, or below bottom.
var height = WinJS.Utilities.getTotalHeight(this._element);
var viewportHeight = thisWinUI._Overlay._keyboardInfo._visibleDocHeight - this._nextMarginPadding;
if (height > viewportHeight) {
// Too Tall, pin to top with max height
this._keyboardMovedUs = true;
this._scrollTop = 0;
this._keyboardSquishedUs = viewportHeight;
} else if (this._nextTop === -1) {
// Pinned to bottom counts as moved
this._keyboardMovedUs = true;
} else if (this._nextTop < 0) {
// Above the top of the viewport
this._scrollTop = 0;
this._keyboardMovedUs = true;
} else if (this._nextBottom > thisWinUI._Overlay._keyboardInfo._visibleDocBottom) {
// Below the bottom of the viewport
this._scrollTop = -1;
this._keyboardMovedUs = true;
}
},
_adjustForKeyboard: function Flyout_adjustForKeyboard() {
// Keyboard moved us, update our metrics as needed
if (this._keyboardSquishedUs) {
// Add scrollbar if we didn't already have scrollsClass
if (!this._hasScrolls) {
WinJS.Utilities.addClass(this._element, scrollsClass);
this._lastMaxHeight = this._element.style.maxHeight;
}
// Adjust height
this._element.style.maxHeight = this._keyboardSquishedUs + "px";
}
// Update top/bottom
this._checkScrollPosition(true);
},
_hidingKeyboard: function Flyout_hidingKeyboard(event) {
// If we aren't visible and not animating, or haven't been repositioned, then nothing to do
// We don't know if the keyboard moved the anchor, so _keyboardMovedUs doesn't help here
if (this.hidden && !this._animating) {
return;
}
// Snap to the final position
// We'll either just reveal the current space or resize the window
if (thisWinUI._Overlay._keyboardInfo._isResized) {
// Flag resize that we'll need an updated position
this._keyboardHiding = true;
} else {
// Not resized, update our final position, giving the anchor a chance to update first.
var that = this;
setImmediate(function () { that._findPosition(); });
}
},
_checkScrollPosition: function Flyout_checkScrollPosition(showing) {
if (this.hidden && !showing) {
return;
}
// May need to adjust top by viewport offset
if (this._scrollTop < 0) {
// Need to attach to bottom
this._element.style.bottom = thisWinUI._Overlay._keyboardInfo._visibleDocBottomOffset + "px";
this._element.style.top = "auto";
} else {
// Normal, attach to top
this._element.style.top = "0px";
this._element.style.bottom = "auto";
}
},
// AppBar flyout animations
_flyoutAnimateIn: function Flyout_flyoutAnimateIn() {
if (this._keyboardMovedUs) {
return this._baseAnimateIn();
} else {
this._element.style.opacity = 1;
this._element.style.visibility = "visible";
return WinJS.UI.Animation.showPopup(this._element, this._nextAnimOffset);
}
},
_flyoutAnimateOut: function Flyout_flyoutAnimateOut() {
if (this._keyboardMovedUs) {
return this._baseAnimateOut();
} else {
this._element.style.opacity = 0;
return WinJS.UI.Animation.hidePopup(this._element, this._nextAnimOffset);
}
},
// Hide all other flyouts besides this one
_hideAllOtherFlyouts: function Flyout_hideAllOtherFlyouts(thisFlyout) {
var flyouts = document.querySelectorAll(".win-flyout");
for (var i = 0; i < flyouts.length; i++) {
var flyoutControl = flyouts[i].winControl;
if (flyoutControl && !flyoutControl.hidden && (flyoutControl !== thisFlyout)) {
flyoutControl.hide();
}
}
},
// Returns true if there is a flyout in the DOM that is not hidden
_isThereVisibleFlyout: function Flyout_isThereVisibleFlyout() {
var flyouts = document.querySelectorAll(".win-flyout");
for (var i = 0; i < flyouts.length; i++) {
var flyoutControl = flyouts[i].winControl;
if (flyoutControl && !flyoutControl.hidden) {
return true;
}
}
return false;
},
_handleKeyDown: function Flyout_handleKeyDown(event) {
// Escape closes flyouts but if the user has a text box with an IME candidate
// window open, we want to skip the ESC key event since it is handled by the IME.
// When the IME handles a key it sets event.keyCode to 229 for an easy check.
if (event.key === "Esc" && event.keyCode !== 229) {
// Show a focus rect on what we move focus to
event.preventDefault();
event.stopPropagation();
this.winControl._keyboardInvoked = true;
this.winControl._hide();
} else if ((event.key === "Spacebar" || event.key === "Enter")
&& (this === document.activeElement)) {
event.preventDefault();
event.stopPropagation();
this.winControl.hide();
} else if (event.shiftKey && event.key === "Tab"
&& this === document.activeElement
&& !event.altKey && !event.ctrlKey && !event.metaKey) {
event.preventDefault();
event.stopPropagation();
this.winControl._focusOnLastFocusableElementOrThis();
}
},
// Create and add a new first div as the first child
_addFirstDiv: function Flyout_addFirstDiv() {
var firstDiv = document.createElement("div");
firstDiv.className = firstDivClass;
firstDiv.style.display = "inline";
firstDiv.setAttribute("role", "menuitem");
firstDiv.setAttribute("aria-hidden", "true");
this._element.insertAdjacentElement("AfterBegin", firstDiv);
var that = this;
firstDiv.addEventListener("focus", function () { that._focusOnLastFocusableElementOrThis(); }, false);
return firstDiv;
},
// Create and add a new final div as the last child
_addFinalDiv: function Flyout_addFinalDiv() {
var finalDiv = document.createElement("div");
finalDiv.className = finalDivClass;
finalDiv.style.display = "inline";
finalDiv.setAttribute("role", "menuitem");
finalDiv.setAttribute("aria-hidden", "true");
this._element.appendChild(finalDiv);
var that = this;
finalDiv.addEventListener("focus", function () { that._focusOnFirstFocusableElementOrThis(); }, false);
return finalDiv;
},
_writeProfilerMark: function Flyout_writeProfilerMark(text) {
msWriteProfilerMark("WinJS.UI.Flyout:" + this._id + ":" + text);
}
});
return Flyout;
})
});
})(WinJS);
// Menu
/// Menu,Menus,Flyout,Flyouts,Statics
(function menuInit(WinJS) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Represents a menu flyout for displaying commands.
///
///
/// Menu
///
///
///
///
/// ]]>
/// Raised just before showing a menu.
/// Raised immediately after a menu is fully shown.
/// Raised just before hiding a menu.
/// Raised immediately after a menu is fully hidden.
/// The Menu control itself
///
///
///
Menu: WinJS.Namespace._lazy(function () {
var thisWinUI = WinJS.UI;
// Class Names
var menuClass = "win-menu";
var menuToggleClass = "win-menu-toggle";
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/menuAriaLabel").value; },
get requiresCommands() { return WinJS.Resources._getWinJSString("ui/requiresCommands").value; },
get nullCommand() { return WinJS.Resources._getWinJSString("ui/nullCommand").value; },
};
var Menu = WinJS.Class.derive(WinJS.UI.Flyout, function Menu_ctor(element, options) {
///
///
/// Creates a new Menu control.
///
///
/// The DOM element that will host the control.
///
///
/// The set of properties and values to apply to the control.
///
/// The new Menu control.
///
///
// We need to be built on top of a Flyout, so stomp on the user's input
options = options || {};
// Make sure there's an input element
this._element = element || document.createElement("div");
this._id = this._element.id || this._element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
// validate that if they didn't set commands, in which
// case any HTML only contains commands. Do this first
// so that we don't leave partial Menus in the DOM.
if (!options.commands && this._element) {
// Shallow copy object so we can modify it.
options = WinJS.Utilities._shallowCopy(options);
options.commands = this._verifyCommandsOnly(this._element, "WinJS.UI.MenuCommand");
}
// Remember aria role in case base constructor changes it
var role = this._element ? this._element.getAttribute("role") : null;
var label = this._element ? this._element.getAttribute("aria-label") : null;
// Call the base overlay constructor helper
this._baseFlyoutConstructor(this._element, options);
// Make sure we have an ARIA role
if (role === null || role === "" || role === undefined) {
this._element.setAttribute("role", "menu");
}
if (label === null || label === "" || label === undefined) {
this._element.setAttribute("aria-label", strings.ariaLabel);
}
// Handle "esc" & "up/down" key presses
this._element.addEventListener("keydown", this._handleKeyDown, true);
// Attach our css class
WinJS.Utilities.addClass(this._element, menuClass);
// Need to set our commands, making sure we're hidden first
this.hide();
this._writeProfilerMark("constructor,StopTM");
}, {
// Public Properties
///
/// Sets the MenuCommand objects that appear in the Menu. You can set this to a single MenuCommand or an array of MenuCommand objects.
///
///
commands: {
set: function (value) {
// Fail if trying to set when visible
if (!this.hidden) {
throw new WinJS.ErrorFromName("WinJS.UI.Menu.CannotChangeCommandsWhenVisible", WinJS.Resources._formatString(thisWinUI._Overlay.commonstrings.cannotChangeCommandsWhenVisible, "Menu"));
}
// Start from scratch
WinJS.Utilities.empty(this._element);
// In case they had only one...
if (!Array.isArray(value)) {
value = [value];
}
// Add commands
var len = value.length;
for (var i = 0; i < len; i++) {
this._addCommand(value[i]);
}
}
},
getCommandById: function (id) {
///
///
/// Retrieve the command with the specified ID from this Menu. If more than one command is found, all are returned.
///
/// The ID of the command to find.
///
/// The command found, an array of commands if more than one have the same ID, or null if no command is found.
///
///
///
var commands = this.element.querySelectorAll("#" + id);
for (var count = 0; count < commands.length; count++) {
// Any elements we generated this should succeed for,
// but remove missing ones just to be safe.
commands[count] = commands[count].winControl;
if (!commands[count]) {
commands.splice(count, 1);
}
}
if (commands.length === 1) {
return commands[0];
} else if (commands.length === 0) {
return null;
}
return commands;
},
showCommands: function (commands) {
///
///
/// Shows the specified commands of the Menu.
///
///
/// The commands to show. The array elements may be Menu objects, or the string identifiers (IDs) of commands.
///
///
///
if (!commands) {
throw new WinJS.ErrorFromName("WinJS.UI.Menu.RequiresCommands", strings.requiresCommands);
}
this._showCommands(commands, true);
},
hideCommands: function (commands) {
///
///
/// Hides the Menu.
///
///
/// Required. Command or Commands to hide, either String, DOM elements, or WinJS objects.
///
///
///
if (!commands) {
throw new WinJS.ErrorFromName("WinJS.UI.Menu.RequiresCommands", strings.requiresCommands);
}
this._hideCommands(commands, true);
},
showOnlyCommands: function (commands) {
///
///
/// Shows the specified commands of the Menu while hiding all other commands.
///
///
/// The commands to show. The array elements may be MenuCommand objects, or the string identifiers (IDs) of commands.
///
///
///
if (!commands) {
throw new WinJS.ErrorFromName("WinJS.UI.Menu.RequiresCommands", strings.requiresCommands);
}
this._showOnlyCommands(commands, true);
},
show: function (anchor, placement, alignment) {
///
///
/// Shows the Menu, if hidden, regardless of other states.
///
///
/// The DOM element, or ID of a DOM element, to anchor the Menu. This parameter overrides the anchor property for this method call only.
///
///
/// The placement of the Menu to the anchor: 'auto' (default), 'top', 'bottom', 'left', or 'right'. This parameter overrides the placement
/// property for this method call only.
///
///
/// For 'top' or 'bottom' placement, the alignment of the Menu to the anchor's edge: 'center' (default), 'left', or 'right'. This parameter
/// overrides the alignment property for this method call only.
///
///
///
// Just call private version to make appbar flags happy
this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
this._show(anchor, placement, alignment);
},
_show: function Menu_show(anchor, placement, alignment) {
// Before we show, we also need to check for children flyouts needing anchors
this._checkForFlyoutCommands();
// Call flyout show
this._baseFlyoutShow(anchor, placement, alignment);
// We need to check for toggles after we send the beforeshow event,
// so the developer has a chance to show or hide more commands.
// Flyout's _findPosition will make that call.
},
_addCommand: function Menu_addCommand(command) {
if (!command) {
throw new WinJS.ErrorFromName("WinJS.UI.Menu.NullCommand", strings.nullCommand);
}
// See if it's a command already
if (!command._element) {
// Not a command, so assume it's options for a command
command = new WinJS.UI.MenuCommand(null, command);
}
// If we were attached somewhere else, detach us
if (command._element.parentElement) {
command._element.parentElement.removeChild(command._element);
}
// Reattach us
this._element.appendChild(command._element);
},
// Called by flyout's _findPosition so that application can update it status
// we do the test and we can then fix this last-minute before showing.
_checkToggle: function Menu_checkToggle() {
var toggles = this._element.querySelectorAll(".win-command[aria-checked]");
var hasToggle = false;
if (toggles) {
for (var i = 0; i < toggles.length; i++) {
if (toggles[i] && toggles[i].winControl && !toggles[i].winControl.hidden) {
// Found a visible toggle control
hasToggle = true;
break;
}
}
}
if (hasToggle) {
WinJS.Utilities.addClass(this._element, menuToggleClass);
} else {
WinJS.Utilities.removeClass(this._element, menuToggleClass);
}
},
_checkForFlyoutCommands: function Menu_checkForFlyoutCommands() {
var commands = this._element.querySelectorAll(".win-command");
for (var count = 0; count < commands.length; count++) {
if (commands[count].winControl) {
// Remember our anchor in case it's a flyout
commands[count].winControl._parentFlyout = this;
}
}
},
_handleKeyDown: function Menu_handleKeyDown(event) {
if (event.key === "Esc") {
// Show a focus rect on what we move focus to
this.winControl._keyboardInvoked = true;
this.winControl._hide();
} else if ((event.key === "Spacebar" || event.key === "Enter")
&& (this === document.activeElement)) {
event.preventDefault();
this.winControl.hide();
} else if (event.key === "Up") {
var that = this;
thisWinUI.Menu._focusOnPreviousElement(that);
// Prevent the page from scrolling
event.preventDefault();
} else if (event.key === "Down") {
that = this;
thisWinUI.Menu._focusOnNextElement(that);
// Prevent the page from scrolling
event.preventDefault();
} else if (event.key === "Tab") {
event.preventDefault();
}
},
_writeProfilerMark: function Menu_writeProfilerMark(text) {
msWriteProfilerMark("WinJS.UI.Menu:" + this._id + ":" + text);
}
});
// Statics
// Set focus to next focusable element in the menu (loop if necessary).
// Note: The loop works by first setting focus to the menu itself. If the menu is
// what had focus before, then we break. Otherwise we try the first child next.
// Focus remains on the menu if nothing is focusable.
Menu._focusOnNextElement = function (menu) {
var _currentElement = document.activeElement;
do {
if (_currentElement === menu) {
_currentElement = _currentElement.firstElementChild;
} else {
_currentElement = _currentElement.nextElementSibling;
}
if (_currentElement) {
_currentElement.focus();
} else {
_currentElement = menu;
}
} while (_currentElement !== document.activeElement)
};
// Set focus to previous focusable element in the menu (loop if necessary).
// Note: The loop works by first setting focus to the menu itself. If the menu is
// what had focus before, then we break. Otherwise we try the last child next.
// Focus remains on the menu if nothing is focusable.
Menu._focusOnPreviousElement = function (menu) {
var _currentElement = document.activeElement;
do {
if (_currentElement === menu) {
_currentElement = _currentElement.lastElementChild;
} else {
_currentElement = _currentElement.previousElementSibling;
}
if (_currentElement) {
_currentElement.focus();
} else {
_currentElement = menu;
}
} while (_currentElement !== document.activeElement)
};
return Menu;
})
});
})(WinJS);
// Menu Command
/// appbar,appbars,Flyout,Flyouts,onclick,Statics
(function menuCommandInit(WinJS) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Represents a command to be displayed in a Menu. MenuCommand objects provide button, toggle button, flyout button,
/// or separator functionality for Menu controls.
///
///
///
///
///
/// ]]>
/// The MenuCommand control itself
///
///
///
MenuCommand: WinJS.Namespace._lazy(function () {
var thisWinUI = WinJS.UI;
// Class Names
var menuCommandClass = "win-command";
var typeSeparator = "separator";
var typeButton = "button";
var typeToggle = "toggle";
var typeFlyout = "flyout";
function _handleMenuClick(event) {
var command = this.winControl;
if (command) {
var hideParent = true;
if (command._type === typeToggle) {
command.selected = !command.selected;
} else if (command._type === typeFlyout && command._flyout) {
var flyout = command._flyout;
// Flyout may not have processAll'd, so this may be a DOM object
if (typeof flyout === "string") {
flyout = document.getElementById(flyout);
}
if (!flyout.show) {
flyout = flyout.winControl;
}
if (flyout && flyout.show) {
if (command._parentFlyout) {
hideParent = false;
flyout.show(command._parentFlyout._currentAnchor, command._parentFlyout._currentPlacement, command._parentFlyout._currentAlignment);
} else {
flyout.show(this);
}
}
}
if (command.onclick) {
command.onclick(event);
}
// Dismiss parent flyout
if (hideParent && command._parentFlyout) {
command._parentFlyout.hide();
}
}
}
function _handleMouseOver(event) {
if (this && this.focus) {
this.focus();
this.addEventListener("mousemove", _handleMouseMove, false);
}
}
function _handleMouseMove(event) {
if (this && this.focus && this !== document.activeElement) {
this.focus();
}
}
function _handleMouseOut(event) {
var that = this;
var parentFlyout = _getParentFlyout(that);
if (parentFlyout
&& this === document.activeElement
&& WinJS.Utilities.hasClass(parentFlyout, "win-menu")
&& parentFlyout.focus) {
// Menu gives focus to the menu itself
parentFlyout.focus();
} else if (parentFlyout
&& this === document.activeElement
&& parentFlyout.children
&& parentFlyout.children.length > 0
&& parentFlyout.children[0]
&& WinJS.Utilities.hasClass(parentFlyout.children[0], "win-firstdiv")
&& parentFlyout.children[0].focus) {
// Flyout gives focus to firstDiv
parentFlyout.children[0].focus();
}
this.removeEventListener("mousemove", _handleMouseMove, false);
}
function _getParentFlyout(element) {
while (element && !WinJS.Utilities.hasClass(element, "win-flyout")) {
element = element.parentElement;
}
return element;
}
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/menuCommandAriaLabel").value; },
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; },
get badClick() { return WinJS.Resources._getWinJSString("ui/badClick").value; },
get badHrElement() { return WinJS.Resources._getWinJSString("ui/badHrElement").value; },
get badButtonElement() { return WinJS.Resources._getWinJSString("ui/badButtonElement").value; }
};
return WinJS.Class.define(function MenuCommand_ctor(element, options) {
///
///
/// Creates a new MenuCommand object.
///
///
/// The DOM element that will host the control.
///
///
/// The set of properties and values to apply to the new MenuCommand.
///
///
/// A MenuCommand control.
///
///
///
// Check to make sure we weren't duplicated
if (element && element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.MenuCommand.DuplicateConstruction", strings.duplicateConstruction);
}
this._disposed = false;
// Don't blow up if they didn't pass options
if (!options) {
options = {};
}
// Need a type before we can create our element
if (!options.type) {
this._type = typeButton;
}
// Go ahead and create it, separator types look different than buttons
// Don't forget to use passed in element if one was provided.
this._element = element;
if (options.type === typeSeparator) {
this._createSeparator();
} else {
// This will also set the icon & label
this._createButton();
}
WinJS.Utilities.addClass(this._element, "win-disposable");
// Remember ourselves
this._element.winControl = this;
// Attach our css class
WinJS.Utilities.addClass(this._element, menuCommandClass);
if (!options.selected && options.type === typeToggle) {
// Make sure toggle's have selected false for CSS
this.selected = false;
}
if (options.onclick) {
this.onclick = options.onclick;
}
options.onclick = _handleMenuClick;
WinJS.UI.setOptions(this, options);
// Set our options
if (this._type !== typeSeparator) {
// Make sure we have an ARIA role
var role = this._element.getAttribute("role");
if (role === null || role === "" || role === undefined) {
role = "menuitem";
if (this._type === typeToggle) {
role = "menuitemcheckbox";
}
this._element.setAttribute("role", role);
if (this._type === typeFlyout) {
this._element.setAttribute("aria-haspopup", true);
}
}
var label = this._element.getAttribute("aria-label");
if (label === null || label === "" || label === undefined) {
this._element.setAttribute("aria-label", strings.ariaLabel);
}
}
this._element.addEventListener("mouseover", _handleMouseOver, false);
this._element.addEventListener("mouseout", _handleMouseOut, false);
}, {
///
/// Gets the ID of the MenuCommand.
///
///
id: {
get: function () {
return this._element.id;
},
set: function (value) {
// we allow setting first time only. otherwise we ignore it.
if (!this._element.id) {
this._element.id = value;
}
}
},
///
/// Gets the type of the MenuCommand. Possible values are "button", "toggle", "flyout", or "separator".
///
///
type: {
get: function () {
return this._type;
},
set: function (value) {
// we allow setting first time only. otherwise we ignore it.
if (!this._type) {
if (value !== typeButton && value !== typeFlyout && value !== typeToggle && value !== typeSeparator) {
this._type = typeButton;
} else {
this._type = value;
}
}
}
},
///
/// The label of the MenuCommand
///
///
label: {
get: function () {
return this._label;
},
set: function (value) {
this._label = value;
this._element.innerText = this.label;
// Update aria-label
this._element.setAttribute("aria-label", this.label);
}
},
///
/// Gets or sets the function to invoke when the command is clicked.
///
///
onclick: {
get: function () {
return this._onclick;
},
set: function (value) {
if (value && typeof value !== "function") {
throw new WinJS.ErrorFromName("WinJS.UI.MenuCommand.BadClick", WinJS.Resources._formatString(strings.badClick, "MenuCommand"));
}
this._onclick = value;
}
},
///
/// For flyout type MenuCommands, this property returns the WinJS.UI.Flyout that this command invokes. When setting this property, you can set
/// it to the string ID of the Flyout, the DOM object that hosts the Flyout, or the Flyout object itself.
///
///
flyout: {
get: function () {
// Resolve it to the flyout
var flyout = this._flyout;
if (typeof flyout === "string") {
flyout = document.getElementById(flyout);
}
// If it doesn't have a .element, then we need to getControl on it
if (flyout && !flyout.element) {
flyout = flyout.winControl;
}
return flyout;
},
set: function (value) {
// Need to update aria-owns with the new ID.
var id = value;
if (id && typeof id !== "string") {
// Our controls have .element properties
if (id.element) {
id = id.element;
}
// Hope it's a DOM element, get ID from DOM element
if (id) {
if (id.id) {
id = id.id;
} else {
// No id, have to fake one
id.id = id.uniqueID;
id = id.id;
}
}
}
if (typeof id === "string") {
this._element.setAttribute("aria-owns", id);
}
// Remember it
this._flyout = value;
}
},
///
/// Gets or sets the selected state of a toggle button. This property is true if the toggle button is selected; otherwise, it's false.
///
///
selected: {
get: function () {
// Ensure it's a boolean because we're using the DOM element to keep in-sync
return this._element.getAttribute("aria-checked") === "true";
},
set: function (value) {
this._element.setAttribute("aria-checked", !!value);
}
},
///
/// Gets the DOM element that hosts this MenuCommand.
///
///
element: {
get: function () {
return this._element;
}
},
///
/// Gets or sets a value that indicates whether the MenuCommand is disabled. This value is true if the MenuCommand is disabled; otherwise, false.
///
///
disabled: {
get: function () {
// Ensure it's a boolean because we're using the DOM element to keep in-sync
return !!this._element.disabled;
},
set: function (value) {
this._element.disabled = !!value;
}
},
///
/// Determine if a command is currently hidden.
///
///
hidden: {
get: function () {
// Ensure it's a boolean because we're using the DOM element to keep in-sync
return this._element.style.visibility === "hidden";
},
set: function (value) {
var menuControl = thisWinUI._Overlay._getParentControlUsingClassName(this._element, "win-menu");
if (menuControl && !menuControl.hidden) {
throw new WinJS.ErrorFromName("WinJS.UI.MenuCommand.CannotChangeHiddenProperty", WinJS.Resources._formatString(thisWinUI._Overlay.commonstrings.cannotChangeHiddenProperty, "Menu"));
}
var style = this._element.style;
if (value) {
style.visibility = "hidden";
style.display = "none";
} else {
style.visibility = "";
style.display = "block";
}
}
},
///
/// Gets or sets the extra CSS class that is applied to the host DOM element.
///
///
extraClass: {
get: function () {
return this._extraClass;
},
set: function (value) {
if (this._extraClass) {
WinJS.Utilities.removeClass(this._element, this._extraClass);
}
this._extraClass = value;
WinJS.Utilities.addClass(this._element, this._extraClass);
}
},
dispose: function () {
///
///
/// Disposes this control.
///
///
///
if (this._disposed) {
return;
}
this._disposed = true;
if (this._flyout) {
this._flyout.dispose();
}
},
addEventListener: function (type, listener, useCapture) {
///
///
/// Registers an event handler for the specified event.
///
/// The name of the event to register.
/// The function that handles the event.
///
/// Set to true to register the event handler for the capturing phase; otherwise, set to false to register the event handler for the bubbling phase.
///
///
///
return this._element.addEventListener(type, listener, useCapture);
},
removeEventListener: function (type, listener, useCapture) {
///
///
/// Removes the specified event handler that the addEventListener method registered.
///
/// The name of the event to remove.
/// The event handler function to remove.
///
/// Set to true to remove the capturing phase event handler; set to false to remove the bubbling phase event handler.
///
///
///
return this._element.removeEventListener(type, listener, useCapture);
},
// Private properties
_createSeparator: function MenuCommand_createSeparator() {
// Make sure there's an input element
if (!this._element) {
this._element = document.createElement("hr");
} else {
// Verify the input was an hr
if (this._element.tagName !== "HR") {
throw new WinJS.ErrorFromName("WinJS.UI.MenuCommand.BadHrElement", strings.badHrElement);
}
}
},
_createButton: function MenuCommand_createButton() {
// Make sure there's an input element
if (!this._element) {
this._element = document.createElement("button");
} else {
// Verify the input was a button
if (this._element.tagName !== "BUTTON") {
throw new WinJS.ErrorFromName("WinJS.UI.MenuCommand.BadButtonElement", strings.badButtonElement);
}
this._element.innerHTML = "";
}
// MenuCommand buttons need to look like this:
//// Command 1
this._element.type = "button";
// 'innertext' label is added later by caller
}
});
})
});
})(WinJS);
(function searchboxInit(global) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Enables the user to perform search queries and select suggestions.
///
///
///
///
///
/// ]]>
/// Raised when user or app changes the query text.
/// Raised when user clicks on search glyph or presses Enter.
/// Raised when user clicks one of the displayed suggestions.
/// Raised when the system requests search suggestions from this app.
///
/// Raised when the app automatically redirects focus to the search box. This event can only be raised when the focusOnKeyboardInput property is set to true.
///
/// Styles the entire Search box control.
/// Styles the query input box.
/// Styles the search button.
/// Styles the result suggestions flyout.
/// Styles the result type suggestion.
/// Styles the query type suggestion.
///
/// Styles the separator type suggestion.
///
///
/// Styles the currently selected suggestion.
///
///
///
///
SearchBox: WinJS.Namespace._lazy(function () {
var utilities = WinJS.Utilities;
var createEvent = WinJS.Utilities._createEventProperty;
// Enums
var ClassName = {
searchBox: "win-searchbox",
searchBoxInput: "win-searchbox-input",
searchBoxButton: "win-searchbox-button",
searchBoxFlyout: "win-searchbox-flyout",
searchBoxSuggestionResult: "win-searchbox-suggestion-result",
searchBoxSuggestionQuery: "win-searchbox-suggestion-query",
searchBoxSuggestionSeparator: "win-searchbox-suggestion-separator",
searchBoxSuggestionSelected: "win-searchbox-suggestion-selected",
searchBoxFlyoutHighlightText: "win-searchbox-flyout-highlighttext",
searchBoxButtonInputFocus: "win-searchbox-button-input-focus",
searchBoxInputFocus: "win-searchbox-input-focus",
searchBoxSuggestionResultText: "win-searchbox-suggestion-result-text",
searchBoxSuggestionResultDetailedText: "win-searchbox-suggestion-result-detailed-text",
searchboxDisabled: "win-searchbox-disabled",
searchboxHitHighlightSpan: "win-searchbox-hithighlight-span",
};
var EventName = {
querychanged: "querychanged",
querysubmitted: "querysubmitted",
resultsuggestionchosen: "resultsuggestionchosen",
suggestionsrequested: "suggestionsrequested",
receivingfocusonkeyboardinput: "receivingfocusonkeyboardinput"
};
var SearchSuggestionKind = {
Query: 0,
Result: 1,
Separator: 2
};
var strings = {
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; },
get invalidSearchBoxSuggestionKind() { return WinJS.Resources._getWinJSString("ui/invalidSearchBoxSuggestionKind").value; },
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabel").value; },
get ariaLabelInputNoPlaceHolder() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabelInputNoPlaceHolder").value; },
get ariaLabelInputPlaceHolder() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabelInputPlaceHolder").value; },
get ariaLabelButton() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabelButton").value; },
get ariaLabelQuery() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabelQuery").value; },
get ariaLabelSeparator() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabelSeparator").value; },
get ariaLabelResult() { return WinJS.Resources._getWinJSString("ui/searchBoxAriaLabelResult").value; }
};
var SearchBox = WinJS.Class.define(function SearchBox_ctor(element, options) {
///
///
/// Creates a new SearchBox.
///
///
/// The DOM element that hosts the SearchBox.
///
///
/// An object that contains one or more property/value pairs to apply to the new control.
/// Each property of the options object corresponds to one of the control's properties or events.
/// Event names must begin with "on". For example, to provide a handler for the querychanged event,
/// add a property named "onquerychanged" to the options object and set its value to the event handler.
/// This parameter is optional.
///
///
/// The new SearchBox.
///
///
///
element = element || document.createElement("div");
if (element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.SearchBox.DuplicateConstruction", strings.duplicateConstruction);
}
element.winControl = this;
// Elements
this._domElement = null;
this._inputElement = null;
this._buttonElement = null;
this._flyout = null;
this._flyoutDivElement = null;
this._repeaterDivElement = null;
this._repeater = null;
// Variables
this._disposed = false;
this._focusOnKeyboardInput = false;
this._chooseSuggestionOnEnter = false;
this._lastKeyPressLanguage = "";
// These are used to eliminate redundant query submitted events
this._prevQueryText = "";
this._prevLinguisticDetails = this._createSearchQueryLinguisticDetails([], 0, 0, "", "");
this._prevCompositionStart = 0;
this._prevCompositionLength = 0;
this._isProcessingDownKey = false;
this._isProcessingUpKey = false;
this._isProcessingTabKey = false;
this._isProcessingEnterKey = false;
this._isFlyoutPointerDown = false;
this._reflowImeOnPointerRelease = false;
// Focus and selection related variables
this._currentFocusedIndex = -1;
this._currentSelectedIndex = -1;
this._suggestionRendererBind = this._suggestionRenderer.bind(this);
this._requestingFocusOnKeyboardInputHandlerBind = this._requestingFocusOnKeyboardInputHandler.bind(this);
this._suggestionsRequestedHandlerBind = this._suggestionsRequestedHandler.bind(this);
this._suggestionsChangedHandlerBind = this._suggestionsChangedHandler.bind(this);
// Find out if we are in local compartment and if search APIs are available.
this._searchSuggestionManager = null;
this._searchSuggestions = null;
// Get the search suggestion provider if it is available
if ((window.Windows) && (Windows.ApplicationModel) && (Windows.ApplicationModel.Search) && (Windows.ApplicationModel.Search.Core) && (Windows.ApplicationModel.Search.Core.SearchSuggestionManager)) {
this._searchSuggestionManager = new Windows.ApplicationModel.Search.Core.SearchSuggestionManager();
this._searchSuggestions = this._searchSuggestionManager.suggestions;
}
this._hitFinder = null;
this._setElement(element);
WinJS.UI.setOptions(this, options);
this._setAccessibilityProperties();
WinJS.Utilities.addClass(element, "win-disposable");
}, {
///
/// The DOM element that hosts the SearchBox.
///
///
element: {
get: function () {
return this._domElement;
}
},
///
/// Gets or sets the placeholder text for the SearchBox. This text is displayed if there is no
/// other text in the input box.
///
///
placeholderText: {
get: function () {
return this._inputElement.placeholder;
},
set: function (value) {
this._inputElement.placeholder = value;
this._updateInputElementAriaLabel();
}
},
///
/// Gets or sets the query text for the SearchBox.
///
///
queryText: {
get: function () {
return this._inputElement.value;
},
set: function (value) {
this._inputElement.value = value;
}
},
///
/// Gets or sets a value that specifies whether search history is disabled for the SearchBox. The default value is false.
///
///
searchHistoryDisabled: {
get: function () {
if (this._searchSuggestionManager) {
return !this._searchSuggestionManager.searchHistoryEnabled;
} else {
return true;
}
},
set: function (value) {
if (this._searchSuggestionManager) {
this._searchSuggestionManager.searchHistoryEnabled = !value;
}
}
},
///
/// Gets or sets the search history context for the SearchBox. The search history context string is used as a secondary key for storing search history.
/// (The primary key is the AppId.) An app can use the search history context string to store different search histories based on the context of the application.
/// If you don't set this property, the system assumes that all searches in your app occur in the same context.
/// If you update this property while the search pane is open with suggestions showing, the changes won't take effect until the user enters the next character.
///
///
searchHistoryContext: {
get: function () {
if (this._searchSuggestionManager) {
return this._searchSuggestionManager.searchHistoryContext;
} else {
return "";
}
},
set: function (value) {
if (this._searchSuggestionManager) {
this._searchSuggestionManager.searchHistoryContext = value;
}
}
},
///
/// Enable automatically focusing the search box when the user types into the app window (off by default) While this is enabled,
/// input on the current thread will be intercepted and redirected to the search box. Only textual input will trigger the search box to focus.
/// The caller will continue to receive non-text keys (such as arrows, tab, etc
/// This will also not affect WIN/CTRL/ALT key combinations (except for Ctrl-V for paste).
/// If the client needs more to happen than just set focus in the box (make control visible, etc.), they will need to handle the event.
/// If enabled, the app must be sure to disable this if the user puts focus in some other edit field.
///
///
focusOnKeyboardInput: {
get: function () {
return this._focusOnKeyboardInput;
},
set: function (value) {
if (this._focusOnKeyboardInput && !value) {
if (this._searchSuggestionManager) {
this._searchSuggestionManager.removeEventListener("requestingfocusonkeyboardinput", this._requestingFocusOnKeyboardInputHandlerBind);
}
} else if (!this._focusOnKeyboardInput && !!value) {
if (this._searchSuggestionManager) {
this._searchSuggestionManager.addEventListener("requestingfocusonkeyboardinput", this._requestingFocusOnKeyboardInputHandlerBind);
}
}
this._focusOnKeyboardInput = !!value;
}
},
///
/// Gets or sets whether the first suggestion is chosen when the user presses Enter.
/// When set to true, as the user types in the search box, a focus rectangle is drawn on the first search suggestion
/// (if present and no IME composition in progress). Pressing enter will behave the same as if clicked on the focused suggestion,
/// and the down arrow key press will put real focus to the second suggestion and the up arrow key will remove focus.
///
///
chooseSuggestionOnEnter: {
get: function () {
return this._chooseSuggestionOnEnter;
},
set: function (value) {
this._chooseSuggestionOnEnter = !!value;
this._updateSearchButtonClass();
}
},
///
/// Gets or sets a value that specifies whether the SearchBox is disabled.
///
///
disabled: {
get: function () {
return this._inputElement.disabled;
},
set: function (value) {
if (this._inputElement.disabled === !!value) {
return;
}
if (!value) {
// Enable control
this._inputElement.disabled = false;
this._buttonElement.disabled = false;
this._domElement.disabled = false;
utilities.removeClass(this.element, ClassName.searchboxDisabled);
if (document.activeElement === this.element) {
try {
this._inputElement.setActive();
} catch (e) {
}
}
} else {
// Disable control
if (this._isFlyoutShown) {
this._hideFlyout();
}
utilities.addClass(this.element, ClassName.searchboxDisabled);
this._inputElement.disabled = true;
this._buttonElement.disabled = true;
this._domElement.disabled = true;
}
}
},
// Methods
setLocalContentSuggestionSettings: function SearchBox_setLocalContentSuggestionSettings(settings) {
///
///
/// Specifies whether suggestions based on local files are automatically displayed in the search pane, and defines the criteria that
/// the system uses to locate and filter these suggestions.
///
///
/// The new settings for local content suggestions.
///
///
///
if (this._searchSuggestionManager) {
this._searchSuggestionManager.setLocalContentSuggestionSettings(settings);
}
},
dispose: function SearchBox() {
///
///
/// Disposes this control.
///
///
///
if (this._disposed) {
return;
}
// Cancel pending promises.
if (this._flyoutOpenPromise) {
this._flyoutOpenPromise.cancel();
}
// Detach winrt events.
if (this._focusOnKeyboardInput) {
if (this._searchSuggestionManager) {
this._searchSuggestionManager.removeEventListener("requestingfocusonkeyboardinput", this._requestingFocusOnKeyboardInputHandlerBind);
}
}
if (this._searchSuggestions) {
this._searchSuggestions.removeEventListener("vectorchanged", this._suggestionsChangedHandlerBind);
}
if (this._searchSuggestionManager) {
this._searchSuggestionManager.removeEventListener("suggestionsrequested", this._suggestionsRequestedHandlerBind);
}
this._searchSuggestionManager = null;
this._searchSuggestions = null;
this._hitFinder = null;
this._disposed = true;
},
///
/// Raised when user or app changes the query text.
///
///
onquerychanged: createEvent(EventName.querychanged),
///
/// Raised when user clicks on search glyph or presses enter button.
///
///
onquerysubmitted: createEvent(EventName.querysubmitted),
///
/// Raised when user clicks on one of the suggestions displayed.
///
///
onresultsuggestionchosen: createEvent(EventName.resultsuggestionchosen),
///
/// Raised when Windows requests search suggestions from the app.
///
///
onsuggestionsrequested: createEvent(EventName.suggestionsrequested),
// Private methods
_isFlyoutShown: function SearchBox_isFlyoutShown() {
return (this._flyoutDivElement.style.display !== "none");
},
_isFlyoutBelow: function SearchBox_isFlyoutBelow() {
if (this._flyoutDivElement.getBoundingClientRect().top > this._inputElement.getBoundingClientRect().top) {
return true;
}
return false;
},
_getFlyoutTop: function SearchBox_getFlyoutTop() {
if (this._isFlyoutBelow()) {
return this._inputElement.getBoundingClientRect().bottom;
}
var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
return this._inputElement.getBoundingClientRect().top - popupHeight;
},
_getFlyoutBottom: function SearchBox_getFlyoutBottom() {
if (this._isFlyoutBelow()) {
var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
return this._inputElement.getBoundingClientRect().bottom + popupHeight;
}
return this._inputElement.getBoundingClientRect().top;
},
_updateFlyoutTopAndTouchAction: function SearchBox_updateFlyoutTopAndTouchAction() {
var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
if (!this._isFlyoutBelow()) {
this._flyoutDivElement.style.top = "-" + popupHeight + "px";
}
// ms-scroll-chaining:none will still chain scroll parent element if child div does
// not have a scroll bar. Prevent this by setting and updating touch action
if (this._flyoutDivElement.scrollHeight > popupHeight) {
this._flyoutDivElement.style.touchAction = "pan-y";
} else {
this._flyoutDivElement.style.touchAction = "none";
}
},
_showFlyout: function SearchBox_showFlyout() {
if (this._isFlyoutShown()) {
return;
}
if (this._suggestionsData.length === 0) {
return;
}
this._flyoutDivElement.style.display = "block";
// Display above vs below
var minPopupHeight = this._flyoutDivElement.clientHeight;
if (minPopupHeight < WinJS.UI.SearchBox._Constants.MIN_POPUP_HEIGHT) {
minPopupHeight = WinJS.UI.SearchBox._Constants.MIN_POPUP_HEIGHT;
}
var flyoutRect = this._flyoutDivElement.getBoundingClientRect();
var searchBoxRect = this.element.getBoundingClientRect();
var popupHeight = flyoutRect.bottom - flyoutRect.top;
var popupWidth = flyoutRect.right - flyoutRect.left;
var searchBoxWidth = searchBoxRect.right - searchBoxRect.left;
var documentClientHeight = document.documentElement.clientHeight;
var documentClientWidth = document.documentElement.clientWidth;
var searchBoxClientHeight = this.element.clientHeight;
var searchBoxClientLeft = this.element.clientLeft;
var flyoutBelowSearchBox = true;
if ((searchBoxRect.bottom + minPopupHeight) <= documentClientHeight) {
// There is enough space below. Show below
this._flyoutDivElement.style.top = searchBoxClientHeight + "px";
} else if ((searchBoxRect.top - minPopupHeight) >= 0) {
// There is enough space above. Show above
this._flyoutDivElement.style.top = "-" + popupHeight + "px";
flyoutBelowSearchBox = false;
} else {
// Not enough space above or below. Show below.
this._flyoutDivElement.style.top = searchBoxClientHeight + "px";
}
// Align left vs right edge
var alignRight;
if (window.getComputedStyle(this._flyoutDivElement).direction === "rtl") {
// RTL: Align to the right edge if there is enough space to the left of the search box's
// right edge, or if there is not enough space to fit the flyout aligned to either edge.
alignRight = ((searchBoxRect.right - popupWidth) >= 0) || ((searchBoxRect.left + popupWidth) > documentClientWidth);
} else {
// LTR: Align to the right edge if there isn't enough space to the right of the search box's
// left edge, but there is enough space to the left of the search box's right edge.
alignRight = ((searchBoxRect.left + popupWidth) > documentClientWidth) && ((searchBoxRect.right - popupWidth) >= 0);
}
if (alignRight) {
this._flyoutDivElement.style.left = (searchBoxWidth - popupWidth - searchBoxClientLeft) + "px";
} else {
this._flyoutDivElement.style.left = "-" + searchBoxClientLeft + "px";
}
// ms-scroll-chaining:none will still chain scroll parent element if child div does
// not have a scroll bar. Prevent this by setting and updating touch action
if (this._flyoutDivElement.scrollHeight > popupHeight) {
this._flyoutDivElement.style.touchAction = "pan-y";
} else {
this._flyoutDivElement.style.touchAction = "none";
}
this._addFlyoutIMEPaddingIfRequired();
if (this._flyoutOpenPromise) {
this._flyoutOpenPromise.cancel();
this._flyoutOpenPromise = null;
}
var animationKeyframe = flyoutBelowSearchBox ? "WinJS-flyoutBelowSearchBox-showPopup" : "WinJS-flyoutAboveSearchBox-showPopup";
this._flyoutOpenPromise = WinJS.UI.Animation.showPopup(this._flyoutDivElement, { top: "0px", left: "0px", keyframe: animationKeyframe });
},
_hideFlyout: function SearchBox_hideFlyout() {
if (this._isFlyoutShown()) {
this._flyoutDivElement.style.display = "none";
this._updateSearchButtonClass();
}
},
_addNewSpan: function SearchBox_addNewSpan(element, innerText, insertBefore) {
// Adds new span element with specified inner text as child to element, placed before insertBefore
var spanElement = document.createElement("span");
spanElement.innerText = innerText;
spanElement.setAttribute("aria-hidden", "true");
utilities.addClass(spanElement, ClassName.searchboxHitHighlightSpan);
element.insertBefore(spanElement, insertBefore)
return spanElement;
},
_addHitHighlightedText: function SearchBox_addHitHighlightedText(element, item, text) {
if (text) {
// Remove any existing hit highlighted text spans
utilities.query("." + ClassName.searchboxHitHighlightSpan, element).forEach(function (childElement) {
childElement.parentNode.removeChild(childElement);
});
// Insert spans at the front of element
var firstChild = element.firstChild;
var hitsProvided = item.hits;
if ((!hitsProvided) && (this._hitFinder !== null) && (item.kind !== SearchSuggestionKind.Separator)) {
hitsProvided = this._hitFinder.find(text);
}
var hits = WinJS.UI.SearchBox._sortAndMergeHits(hitsProvided);
var lastPosition = 0;
for (var i = 0; i < hits.length; i++) {
var hit = hits[i];
// Add previous normal text
this._addNewSpan(element, text.substring(lastPosition, hit.startPosition), firstChild);
lastPosition = hit.startPosition + hit.length;
// Add hit highlighted text
var spanHitHighlightedText = this._addNewSpan(element, text.substring(hit.startPosition, lastPosition), firstChild);
utilities.addClass(spanHitHighlightedText, ClassName.searchBoxFlyoutHighlightText);
}
// Add final normal text
if (lastPosition < text.length) {
this._addNewSpan(element, text.substring(lastPosition), firstChild);
}
}
},
_findSuggestionElementIndex: function SearchBox_findSuggestionElementIndex(curElement) {
if (curElement) {
for (var i = 0; i < this._suggestionsData.length; i++) {
if (this._repeater.elementFromIndex(i) === curElement) {
return i;
}
}
}
return -1;
},
_isSuggestionSelectable: function SearchBox_isSuggestionSelectable(suggestion) {
return ((suggestion.kind === SearchSuggestionKind.Query) ||
(suggestion.kind === SearchSuggestionKind.Result));
},
_findNextSuggestionElementIndex: function SearchBox_findNextSuggestionElementIndex(curIndex) {
// Returns -1 if there are no focusable elements after curIndex
// Returns first element if curIndex < 0
var startIndex = curIndex + 1;
if (startIndex < 0) {
startIndex = 0;
}
for (var i = startIndex; i < this._suggestionsData.length; i++) {
if ((this._repeater.elementFromIndex(i)) && (this._isSuggestionSelectable(this._suggestionsData.getAt(i)))) {
return i;
}
}
return -1;
},
_findPreviousSuggestionElementIndex: function SearchBox_findPreviousSuggestionElementIndex(curIndex) {
// Returns -1 if there are no focusable elements before curIndex
// Returns last element if curIndex >= suggestionsdata.length
var startIndex = curIndex - 1;
if (startIndex >= this._suggestionsData.length) {
startIndex = this._suggestionsData.length - 1;
}
for (var i = startIndex; i >= 0; i--) {
if ((this._repeater.elementFromIndex(i)) && (this._isSuggestionSelectable(this._suggestionsData.getAt(i)))) {
return i;
}
}
return -1;
},
_trySetFocusOnSuggestionIndex: function SearchBox_trySetFocusOnSuggestionIndex(index) {
try {
this._repeater.elementFromIndex(index).focus();
} catch (e) {
}
},
_updateFakeFocus: function SearchBox_updateFakeFocus() {
var firstElementIndex;
if (this._isFlyoutShown() && (this._chooseSuggestionOnEnter)) {
firstElementIndex = this._findNextSuggestionElementIndex(-1);
} else {
// This will clear the fake focus.
firstElementIndex = -1;
}
this._selectSuggestionAtIndex(firstElementIndex);
},
_updateSearchButtonClass: function SearchBox_updateSearchButtonClass() {
if ((this._currentSelectedIndex !== -1) || (document.activeElement !== this._inputElement)) {
// Focus is not in input. remove class
utilities.removeClass(this._buttonElement, ClassName.searchBoxButtonInputFocus);
} else if (document.activeElement === this._inputElement) {
utilities.addClass(this._buttonElement, ClassName.searchBoxButtonInputFocus);
}
},
_selectSuggestionAtIndex: function SearchBox_selectSuggestionAtIndex(indexToSelect) {
// Sets focus on the specified element and removes focus from others.
// Clears selection if index is outside of suggestiondata index range.
var curElement = null;
for (var i = 0; i < this._suggestionsData.length; i++) {
curElement = this._repeater.elementFromIndex(i);
if (i !== indexToSelect) {
utilities.removeClass(curElement, ClassName.searchBoxSuggestionSelected);
curElement.setAttribute("aria-selected", "false");
} else {
utilities.addClass(curElement, ClassName.searchBoxSuggestionSelected);
this._scrollToView(curElement);
curElement.setAttribute("aria-selected", "true");
}
}
this._updateSearchButtonClass();
this._currentSelectedIndex = indexToSelect;
if (curElement) {
this._inputElement.setAttribute("aria-activedescendant", this._repeaterDivElement.id + indexToSelect);
} else if (this._inputElement.hasAttribute("aria-activedescendant")) {
this._inputElement.removeAttribute("aria-activedescendant");
}
},
_scrollToView: function SearchBox_scrollToView(targetElement) {
var popupHeight = this._flyoutDivElement.getBoundingClientRect().bottom - this._flyoutDivElement.getBoundingClientRect().top;
if ((targetElement.offsetTop + targetElement.offsetHeight) > (this._flyoutDivElement.scrollTop + popupHeight)) {
// Element to scroll is below popup visible area
var scrollDifference = (targetElement.offsetTop + targetElement.offsetHeight) - (this._flyoutDivElement.scrollTop + popupHeight);
this._flyoutDivElement.msZoomTo({ contentX: 0, contentY: (this._flyoutDivElement.scrollTop + scrollDifference), viewportX: 0, viewportY: 0 });
} else if (targetElement.offsetTop < this._flyoutDivElement.scrollTop) {
// Element to scroll is above popup visible area
this._flyoutDivElement.msZoomTo({ contentX: 0, contentY: targetElement.offsetTop, viewportX: 0, viewportY: 0 });
}
},
_querySuggestionRenderer: function SearchBox_querySuggestionRenderer(item) {
var root = document.createElement("div");
this._addHitHighlightedText(root, item, item.text);
root.title = item.text;
utilities.addClass(root, ClassName.searchBoxSuggestionQuery);
var that = this;
root.addEventListener('click', function (ev) {
that._inputElement.focus();
that._processSuggestionChosen(item, ev);
});
root.setAttribute("role", "option");
var ariaLabel = WinJS.Resources._formatString(strings.ariaLabelQuery, item.text);
root.setAttribute("aria-label", ariaLabel);
return root;
},
_separatorSuggestionRenderer: function SearchBox_separatorSuggestionRenderer(item) {
var root = document.createElement("div");
if (item.text.length > 0) {
var textElement = document.createElement("div");
textElement.innerText = item.text;
textElement.title = item.text;
textElement.setAttribute("aria-hidden", "true");
root.appendChild(textElement);
}
root.insertAdjacentHTML("beforeend", "");
utilities.addClass(root, ClassName.searchBoxSuggestionSeparator);
root.setAttribute("role", "separator");
var ariaLabel = WinJS.Resources._formatString(strings.ariaLabelSeparator, item.text);
root.setAttribute("aria-label", ariaLabel);
return root;
},
_resultSuggestionRenderer: function SearchBox_resultSuggestionRenderer(item) {
var root = document.createElement("div");
var image = new Image;
image.style.opacity = 0;
var loadImage = function (url) {
function onload() {
image.removeEventListener("load", onload, false);
WinJS.UI.Animation.fadeIn(image);
}
image.addEventListener("load", onload, false);
image.src = url;
};
if (item.image !== null) {
item.image.openReadAsync().then(function (streamWithContentType) {
if (streamWithContentType !== null) {
loadImage(URL.createObjectURL(streamWithContentType, { oneTimeOnly: true }));
}
});
} else if (item.imageUrl != null) {
loadImage(item.imageUrl);
}
image.setAttribute("aria-hidden", "true");
root.appendChild(image);
var divElement = document.createElement("div");
utilities.addClass(divElement, ClassName.searchBoxSuggestionResultText);
this._addHitHighlightedText(divElement, item, item.text);
divElement.title = item.text;
divElement.setAttribute("aria-hidden", "true");
root.appendChild(divElement);
var brElement = document.createElement("br");
divElement.appendChild(brElement);
var divDetailElement = document.createElement("span");
utilities.addClass(divDetailElement, ClassName.searchBoxSuggestionResultDetailedText);
this._addHitHighlightedText(divDetailElement, item, item.detailText);
divDetailElement.title = item.detailText;
divDetailElement.setAttribute("aria-hidden", "true");
divElement.appendChild(divDetailElement);
utilities.addClass(root, ClassName.searchBoxSuggestionResult);
var that = this;
root.addEventListener('click', function (ev) {
that._inputElement.focus();
that._processSuggestionChosen(item, ev);
});
root.setAttribute("role", "option");
var ariaLabel = WinJS.Resources._formatString(strings.ariaLabelResult, item.text, item.detailText);
root.setAttribute("aria-label", ariaLabel);
return root;
},
_suggestionRenderer: function SearchBox_suggestionRenderer(item) {
var root = null;
if (!item) {
return root;
}
if (item.kind === SearchSuggestionKind.Query) {
root = this._querySuggestionRenderer(item);
} else if (item.kind === SearchSuggestionKind.Separator) {
root = this._separatorSuggestionRenderer(item);
} else if (item.kind === SearchSuggestionKind.Result) {
root = this._resultSuggestionRenderer(item);
} else {
throw new WinJS.ErrorFromName("WinJS.UI.SearchBox.invalidSearchBoxSuggestionKind", strings.invalidSearchBoxSuggestionKind);
}
return root;
},
_setElement: function SearchBox_setElement(element) {
this._domElement = element;
utilities.addClass(this._domElement, ClassName.searchBox);
this._inputElement = document.createElement("input");
this._inputElement.type = "search";
utilities.addClass(this._inputElement, ClassName.searchBoxInput);
this._buttonElement = document.createElement("div");
this._buttonElement.tabIndex = -1;
utilities.addClass(this._buttonElement, ClassName.searchBoxButton);
this._flyoutDivElement = document.createElement('div');
utilities.addClass(this._flyoutDivElement, ClassName.searchBoxFlyout);
this._repeaterDivElement = document.createElement('div');
this._suggestionsData = new WinJS.Binding.List();
this._repeater = new WinJS.UI.Repeater(this._repeaterDivElement, { data: this._suggestionsData, template: this._suggestionRendererBind });
this._domElement.appendChild(this._inputElement);
this._domElement.appendChild(this._buttonElement);
this._domElement.appendChild(this._flyoutDivElement);
this._flyoutDivElement.appendChild(this._repeaterDivElement);
this._hideFlyout();
this._wireupUserEvents();
this._wireupWinRTEvents();
this._wireupRepeaterEvents();
},
_setAccessibilityProperties: function Searchbox_setAccessibilityProperties() {
// Set up accessibility properties
var label = this._domElement.getAttribute("aria-label");
if (!label) {
this._domElement.setAttribute("aria-label", strings.ariaLabel);
}
this._domElement.setAttribute("role", "group");
this._updateInputElementAriaLabel();
this._inputElement.setAttribute("role", "textbox");
this._buttonElement.setAttribute("role", "button");
this._buttonElement.setAttribute("aria-label", strings.ariaLabelButton);
this._repeaterDivElement.setAttribute("role", "listbox");
WinJS.UI._ensureId(this._repeaterDivElement);
this._inputElement.setAttribute("aria-controls", this._repeaterDivElement.id);
this._repeaterDivElement.setAttribute("aria-live", "polite");
},
_updateInputElementAriaLabel: function Searchbox_updateInputElementAriaLabel() {
var ariaLabel = strings.ariaLabelInputNoPlaceHolder;
if (this._inputElement.placeholder && this._inputElement.placeholder) {
ariaLabel = WinJS.Resources._formatString(strings.ariaLabelInputPlaceHolder, this._inputElement.placeholder);
}
this._inputElement.setAttribute("aria-label", ariaLabel);
},
_submitQuery: function Searchbox_submitQuery(queryText, fillLinguisticDetails, event) {
if (this._disposed) {
return;
}
// get the most up to date value of the input langauge from WinRT if available
if ((window.Windows) && (Windows.Globalization) && (Windows.Globalization.Language)) {
this._lastKeyPressLanguage = Windows.Globalization.Language.currentInputMethodLanguageTag;
}
this._fireEvent(WinJS.UI.SearchBox._EventName.querysubmitted, {
language: this._lastKeyPressLanguage,
linguisticDetails: this._getLinguisticDetails(true /*useCache*/, fillLinguisticDetails), // allow caching, but generate empty linguistic details if suggestion is used
queryText: queryText,
keyModifiers: WinJS.UI.SearchBox._getKeyModifiers(event)
});
if (this._searchSuggestionManager) {
this._searchSuggestionManager.addToHistory(
this._inputElement.value,
this._lastKeyPressLanguage
);
}
},
_processSuggestionChosen: function Searchbox_processSuggestionChosen(item, event) {
this.queryText = item.text;
if (item.kind === SearchSuggestionKind.Query) {
this._submitQuery(item.text, false /*fillLinguisticDetails*/, event); // force empty linguistic details since explicitly chosen suggestion from list
} else if (item.kind === SearchSuggestionKind.Result) {
this._fireEvent(WinJS.UI.SearchBox._EventName.resultsuggestionchosen, {
tag: item.tag,
keyModifiers: WinJS.UI.SearchBox._getKeyModifiers(event),
storageFile: null
});
}
this._hideFlyout();
},
_buttonClickHandler: function SearchBox_buttonClickHandler(event) {
this._inputElement.focus();
this._submitQuery(this._inputElement.value, true /*fillLinguisticDetails*/, event);
this._hideFlyout();
},
_inputOrImeChangeHandler: function SearchBox_inputImeChangeHandler(event) {
var isButtonDown = this._buttonElement.msMatchesSelector(":active");
// swallow the IME change event that gets fired when composition is ended due to keyboarding down to the suggestion list & mouse down on the button
if (!this._isProcessingImeFocusLossKey() && !isButtonDown && !this._isFlyoutPointerDown) {
var linguisticDetails = this._getLinguisticDetails(false /*useCache*/, true /*createFilled*/); // never cache on explicit user changes
var hasLinguisticDetailsChanged = this._hasLinguisticDetailsChanged(linguisticDetails); // updates this._prevLinguisticDetails
// Keep the previous composition cache up to date, execpt when composition ended with no text change and alternatives are kept.
// In that case, we need to use the cached values to correctly generate the query prefix/suffix for substituting alternatives, but still report to the client that the composition has ended (via start & length of composition of 0)
if ((this._inputElement.value !== this._prevQueryText) || (this._prevCompositionLength == 0) || (linguisticDetails.queryTextCompositionLength > 0)) {
this._prevCompositionStart = linguisticDetails.queryTextCompositionStart;
this._prevCompositionLength = linguisticDetails.queryTextCompositionLength;
}
if ((this._prevQueryText === this._inputElement.value) && !hasLinguisticDetailsChanged) {
// Sometimes the input change is fired even if there is no change in input.
// Swallow event in those cases.
return;
}
this._prevQueryText = this._inputElement.value;
// get the most up to date value of the input langauge from WinRT if available
if ((window.Windows) && (Windows.Globalization) && (Windows.Globalization.Language)) {
this._lastKeyPressLanguage = Windows.Globalization.Language.currentInputMethodLanguageTag;
}
if ((window.Windows) && (Windows.Data) && (Windows.Data.Text) && (Windows.Data.Text.SemanticTextQuery)) {
if (this._inputElement.value !== "") {
this._hitFinder = new Windows.Data.Text.SemanticTextQuery(this._inputElement.value, this._lastKeyPressLanguage);
} else {
this._hitFinder = null;
}
}
this._fireEvent(WinJS.UI.SearchBox._EventName.querychanged, {
language: this._lastKeyPressLanguage,
queryText: this._inputElement.value,
linguisticDetails: linguisticDetails
});
var queryTextCompositionStart = null;
var queryTextCompositionLength = null;
var queryTextAlternatives = null;
if (this._searchSuggestionManager) {
this._searchSuggestionManager.setQuery(
this._inputElement.value,
this._lastKeyPressLanguage,
linguisticDetails
);
}
}
},
_createSearchQueryLinguisticDetails: function SearchBox_createSearchQueryLinguisticDetails(compositionAlternatives, compositionStartOffset, compositionLength, queryTextPrefix, queryTextSuffix) {
var linguisticDetails = null;
// The linguistic alternatives we receive are only for the composition string being composed. We need to provide the linguistic alternatives
// in the form of the full query text with alternatives embedded.
var fullCompositionAlternatives = [];
for (var i = 0; i < compositionAlternatives.length; i++) {
fullCompositionAlternatives[i] = queryTextPrefix + compositionAlternatives[i] + queryTextSuffix;
}
if ((window.Windows) && (Windows.ApplicationModel) && (Windows.ApplicationModel.Search) && (Windows.ApplicationModel.Search.SearchQueryLinguisticDetails)) {
linguisticDetails = new Windows.ApplicationModel.Search.SearchQueryLinguisticDetails(fullCompositionAlternatives, compositionStartOffset, compositionLength);
}
else {
// If we're in web compartment, create a script version of the WinRT SearchQueryLinguisticDetails object
linguisticDetails = {
queryTextAlternatives: fullCompositionAlternatives,
queryTextCompositionStart: compositionStartOffset,
queryTextCompositionLength: compositionLength
};
}
return linguisticDetails;
},
_getLinguisticDetails: function SearchBox_getLinguisticDetails(useCache, createFilled) { // createFilled=false always creates an empty linguistic details object, otherwise generate it or use the cache
var linguisticDetails = null;
if ((this._inputElement.value === this._prevQueryText) && useCache && this._prevLinguisticDetails && createFilled) {
linguisticDetails = this._prevLinguisticDetails;
}
else {
var compositionAlternatives = [];
var compositionStartOffset = 0;
var compositionLength = 0;
var queryTextPrefix = "";
var queryTextSuffix = "";
if (createFilled && this._inputElement.msGetInputContext && this._inputElement.msGetInputContext().getCompositionAlternatives) {
var context = this._inputElement.msGetInputContext();
compositionAlternatives = context.getCompositionAlternatives();
compositionStartOffset = context.compositionStartOffset;
compositionLength = context.compositionEndOffset - context.compositionStartOffset;
if ((this._inputElement.value !== this._prevQueryText) || (this._prevCompositionLength == 0) || (compositionLength > 0)) {
queryTextPrefix = this._inputElement.value.substring(0, compositionStartOffset);
queryTextSuffix = this._inputElement.value.substring(compositionStartOffset + compositionLength);
}
else {
// composition ended, but alternatives have been kept, need to reuse the previous query prefix/suffix, but still report to the client that the composition has ended (start & length of composition of 0)
queryTextPrefix = this._inputElement.value.substring(0, this._prevCompositionStart);
queryTextSuffix = this._inputElement.value.substring(this._prevCompositionStart + this._prevCompositionLength);
}
}
linguisticDetails = this._createSearchQueryLinguisticDetails(compositionAlternatives, compositionStartOffset, compositionLength, queryTextPrefix, queryTextSuffix);
}
return linguisticDetails;
},
_handleTabKeyDown: function SearchBox_handleTabKeyDown(event) {
var closeFlyout = true;
if (event.shiftKey) {
// If focus is not in input
if (this._currentFocusedIndex !== -1) {
// Remove selection.
this._currentFocusedIndex = -1;
this._selectSuggestionAtIndex(this._currentFocusedIndex);
this._updateSearchButtonClass();
event.preventDefault();
event.stopPropagation();
closeFlyout = false;
}
} else if (this._currentFocusedIndex === -1) {
if (this._isFlyoutBelow()) {
// Move to first element
this._currentFocusedIndex = this._findNextSuggestionElementIndex(this._currentFocusedIndex);
} else {
// Move to last element
this._currentFocusedIndex = this._findPreviousSuggestionElementIndex(this._suggestionsData.length);
}
if (this._currentFocusedIndex != -1) {
this._selectSuggestionAtIndex(this._currentFocusedIndex);
this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
this._updateSearchButtonClass();
event.preventDefault();
event.stopPropagation();
closeFlyout = false;
}
}
if (closeFlyout) {
this._hideFlyout();
}
},
_keyDownHandler: function SearchBox_keyDownHandler(event) {
this._lastKeyPressLanguage = event.locale;
if (event.key === "Tab") {
this._isProcessingTabKey = true;
}
else if (event.key === "Up") {
this._isProcessingUpKey = true;
}
else if (event.key === "Down") {
this._isProcessingDownKey = true;
}
else if ((event.key === "Enter") && (event.locale === "ko")) {
this._isProcessingEnterKey = true;
}
// Ignore keys handled by ime.
if (event.keyCode !== WinJS.UI.SearchBox._Constants.IME_HANDLED_KEYCODE) {
if (event.key === "Tab") {
this._handleTabKeyDown(event);
} else if (event.key === "Esc") {
// If focus is not in input
if (this._currentFocusedIndex !== -1) {
this.queryText = this._prevQueryText;
this._currentFocusedIndex = -1;
this._selectSuggestionAtIndex(this._currentFocusedIndex);
this._updateSearchButtonClass();
event.preventDefault();
event.stopPropagation();
} else if (this.queryText !== "") {
this.queryText = "";
this._inputOrImeChangeHandler(null);
this._updateSearchButtonClass();
event.preventDefault();
event.stopPropagation();
}
} else if (event.key === "Up") {
var prevIndex;
if (this._currentSelectedIndex !== -1) {
prevIndex = this._findPreviousSuggestionElementIndex(this._currentSelectedIndex);
// Restore user entered query when user navigates back to input.
if (prevIndex === -1) {
this.queryText = this._prevQueryText;
}
} else {
prevIndex = this._findPreviousSuggestionElementIndex(this._suggestionsData.length);
}
this._currentFocusedIndex = prevIndex;
this._selectSuggestionAtIndex(prevIndex);
this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
this._updateSearchButtonClass();
event.preventDefault();
event.stopPropagation();
} else if (event.key === "Down") {
var nextIndex = this._findNextSuggestionElementIndex(this._currentSelectedIndex);
// Restore user entered query when user navigates back to input.
if ((this._currentSelectedIndex !== -1) && (nextIndex === -1)) {
this.queryText = this._prevQueryText;
}
this._currentFocusedIndex = nextIndex;
this._selectSuggestionAtIndex(nextIndex);
this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
this._updateSearchButtonClass();
event.preventDefault();
event.stopPropagation();
} else if (event.key === "Enter") {
if (this._currentSelectedIndex === -1) {
this._submitQuery(this._inputElement.value, true /*fillLinguisticDetails*/, event);
} else {
this._processSuggestionChosen(this._suggestionsData.getAt(this._currentSelectedIndex), event);
}
this._hideFlyout();
} else if (WinJS.UI.SearchBox._isTypeToSearchKey(event)) {
// Type to search on suggestions scenario.
if (this._currentFocusedIndex !== -1) {
this._currentFocusedIndex = -1;
this._selectSuggestionAtIndex(-1);
this._updateFakeFocus();
}
}
}
},
_keyPressHandler: function SearchBox_keyPressHandler(event) {
this._lastKeyPressLanguage = event.locale;
},
_keyUpHandler: function SearchBox_keyUpHandler(event) {
if (event.key === "Tab") {
this._isProcessingTabKey = false;
}
else if (event.key === "Up") {
this._isProcessingUpKey = false;
}
else if (event.key === "Down") {
this._isProcessingDownKey = false;
}
else if (event.key === "Enter") {
this._isProcessingEnterKey = false;
}
},
_searchBoxFocusInHandler: function SearchBox__searchBoxFocusInHandler(event) {
// Refresh hit highlighting if text has changed since focus was present
// This can happen if the user committed a suggestion previously.
if (this._inputElement.value !== this._prevQueryText) {
if ((window.Windows) && (Windows.Data) && (Windows.Data.Text) && (Windows.Data.Text.SemanticTextQuery)) {
if (this._inputElement.value !== "") {
this._hitFinder = new Windows.Data.Text.SemanticTextQuery(this._inputElement.value, this._inputElement.lang);
} else {
this._hitFinder = null;
}
}
}
// If focus is returning to the input box from outside the search control, show the flyout and refresh the suggestions
if ((event.target === this._inputElement) && !this._isElementInSearchControl(event.relatedTarget)) {
this._showFlyout();
// If focus is not in input
if (this._currentFocusedIndex !== -1) {
this._selectSuggestionAtIndex(this._currentFocusedIndex);
} else {
this._updateFakeFocus();
}
if (this._searchSuggestionManager) {
this._searchSuggestionManager.setQuery(
this._inputElement.value,
this._lastKeyPressLanguage,
this._getLinguisticDetails(true /*useCache*/, true /*createFilled*/)
);
}
}
utilities.addClass(this.element, ClassName.searchBoxInputFocus);
this._updateSearchButtonClass();
},
_searchBoxFocusOutHandler: function SearchBox_searchBoxFocusOutHandler(event) {
this._hideFlyoutIfLeavingSearchControl(event.relatedTarget);
utilities.removeClass(this.element, ClassName.searchBoxInputFocus);
this._updateSearchButtonClass();
this._isProcessingDownKey = false;
this._isProcessingUpKey = false;
this._isProcessingTabKey = false;
this._isProcessingEnterKey = false;
},
_isIMEOccludingFlyout: function SearchBox_isIMEOccludingFlyout(imeRect) {
var flyoutTop = this._getFlyoutTop();
var flyoutBottom = this._getFlyoutBottom();
if (((imeRect.top >= flyoutTop) && (imeRect.top <= flyoutBottom)) ||
((imeRect.bottom >= flyoutTop) && (imeRect.bottom <= flyoutBottom))) {
return true;
}
return false;
},
_addFlyoutIMEPaddingIfRequired: function SearchBox_addFlyoutIMEPaddingIfRequired() {
if (this._isFlyoutShown() && this._isFlyoutBelow() && this._inputElement.msGetInputContext && this._inputElement.msGetInputContext()) {
var context = this._inputElement.msGetInputContext();
var rect = context.getCandidateWindowClientRect();
if (this._isIMEOccludingFlyout(rect)) {
var animation = WinJS.UI.Animation.createRepositionAnimation(this._flyoutDivElement.children);
this._flyoutDivElement.style.paddingTop = (rect.bottom - rect.top) + "px";
animation.execute();
}
}
},
_msCandidateWindowShowHandler: function SearchBox_msCandidateWindowShowHandler(event) {
this._addFlyoutIMEPaddingIfRequired();
this._reflowImeOnPointerRelease = false;
},
_msCandidateWindowHideHandler: function SearchBox_msCandidateWindowHideHandler(event) {
if (!this._isFlyoutPointerDown) {
var animation = WinJS.UI.Animation.createRepositionAnimation(this._flyoutDivElement.children);
this._flyoutDivElement.style.paddingTop = "";
animation.execute();
}
else {
this._reflowImeOnPointerRelease = true;
}
},
_wireupUserEvents: function SearchBox_wireupUserEvents() {
var inputOrImeChangeHandler = this._inputOrImeChangeHandler.bind(this);
this._buttonElement.addEventListener("click", this._buttonClickHandler.bind(this));
this._inputElement.addEventListener("input", inputOrImeChangeHandler);
this._inputElement.addEventListener("keydown", this._keyDownHandler.bind(this));
this._inputElement.addEventListener("keypress", this._keyPressHandler.bind(this));
this._inputElement.addEventListener("keyup", this._keyUpHandler.bind(this));
this._inputElement.addEventListener("pointerdown", this._inputPointerDownHandler.bind(this));
this._flyoutDivElement.addEventListener("pointerdown", this._flyoutPointerDownHandler.bind(this));
this._flyoutDivElement.addEventListener("pointerup", this._flyoutPointerReleasedHandler.bind(this));
this._flyoutDivElement.addEventListener("pointercancel", this._flyoutPointerReleasedHandler.bind(this));
this._flyoutDivElement.addEventListener("pointerout", this._flyoutPointerReleasedHandler.bind(this));
this.element.addEventListener("focusin", this._searchBoxFocusInHandler.bind(this));
this.element.addEventListener("focusout", this._searchBoxFocusOutHandler.bind(this));
this._inputElement.addEventListener("compositionstart", inputOrImeChangeHandler);
this._inputElement.addEventListener("compositionupdate", inputOrImeChangeHandler);
this._inputElement.addEventListener("compositionend", inputOrImeChangeHandler);
if (this._inputElement.msGetInputContext && this._inputElement.msGetInputContext()) {
var context = this._inputElement.msGetInputContext();
context.addEventListener("MSCandidateWindowShow", this._msCandidateWindowShowHandler.bind(this));
context.addEventListener("MSCandidateWindowHide", this._msCandidateWindowHideHandler.bind(this));
}
},
_repeaterChangedHandler: function SearchBox_repeaterChangedHandler(ev) {
this._updateFlyoutTopAndTouchAction();
if (this._isFlyoutShown()) {
this._repeaterDivElement.style.display = "none";
this._repeaterDivElement.style.display = "block";
}
},
_wireupRepeaterEvents: function SearchBox_wireupRepeaterEvents() {
var repeaterChangeHandler = this._repeaterChangedHandler.bind(this);
this._repeater.addEventListener("itemchanged", repeaterChangeHandler);
this._repeater.addEventListener("iteminserted", repeaterChangeHandler);
this._repeater.addEventListener("itemremoved", repeaterChangeHandler);
this._repeater.addEventListener("itemsreloaded", repeaterChangeHandler);
},
_inputPointerDownHandler: function SearchBox_inputPointerDownHandler(ev) {
if ((document.activeElement === this._inputElement) && (this._currentSelectedIndex !== -1)) {
this._currentFocusedIndex = -1;
this._selectSuggestionAtIndex(this._currentFocusedIndex);
}
},
_flyoutPointerDownHandler: function SearchBox_flyoutPointerDownHandler(ev) {
this._isFlyoutPointerDown = true;
var srcElement = ev.srcElement;
while (srcElement && (srcElement.parentNode !== this._repeaterDivElement)) {
srcElement = srcElement.parentNode;
}
var index = this._findSuggestionElementIndex(srcElement);
if ((index >= 0) && (index < this._suggestionsData.length) && (this._currentFocusedIndex !== index)) {
if (this._isSuggestionSelectable(this._suggestionsData.getAt(index))) {
this._currentFocusedIndex = index;
this._selectSuggestionAtIndex(index);
this._updateQueryTextWithSuggestionText(this._currentFocusedIndex);
}
}
// Prevent default so focus does not leave input element.
ev.preventDefault();
},
_flyoutPointerReleasedHandler: function SearchBox_flyoutPointerReleasedHandler(ev) {
this._isFlyoutPointerDown = false;
if (this._reflowImeOnPointerRelease) {
this._reflowImeOnPointerRelease = false;
var animation = WinJS.UI.Animation.createRepositionAnimation(this._flyoutDivElement.children);
this._flyoutDivElement.style.paddingTop = "";
animation.execute();
}
},
_isElementInSearchControl: function SearchBox_isElementInSearchControl(targetElement) {
return this.element.contains(targetElement) || (this.element === targetElement);
},
_hideFlyoutIfLeavingSearchControl: function SearchBox__hideFlyoutIfLeavingSearchControl(targetElement) {
if (!this._isFlyoutShown()) {
return;
}
if (!this._isElementInSearchControl(targetElement)) {
this._hideFlyout();
}
},
_wireupWinRTEvents: function SearchBox_wireupWinRTEvents() {
if (this._searchSuggestions) {
this._searchSuggestions.addEventListener("vectorchanged", this._suggestionsChangedHandlerBind);
}
if (this._searchSuggestionManager) {
this._searchSuggestionManager.addEventListener("suggestionsrequested", this._suggestionsRequestedHandlerBind);
}
},
_suggestionsChangedHandler: function SearchBox_suggestionsChangedHandler(event) {
var collectionChange = event.collectionChange;
if (collectionChange === Windows.Foundation.Collections.CollectionChange.reset) {
if (this._isFlyoutShown()) {
this._hideFlyout();
}
this._suggestionsData.splice(0, this._suggestionsData.length);
} else if (collectionChange === Windows.Foundation.Collections.CollectionChange.itemInserted) {
var index = event.index;
var suggestion = this._searchSuggestions[index];
this._suggestionsData.splice(index, 0, suggestion);
this._showFlyout();
} else if (collectionChange === Windows.Foundation.Collections.CollectionChange.itemRemoved) {
if ((this._suggestionsData.length === 1)) {
try {
this._inputElement.setActive();
} catch (e) {
}
this._hideFlyout();
}
var index = event.index;
this._suggestionsData.splice(index, 1);
} else if (collectionChange === Windows.Foundation.Collections.CollectionChange.itemChanged) {
var index = event.index;
var suggestion = this._searchSuggestions[index];
if (suggestion !== this._suggestionsData.getAt(index)) {
this._suggestionsData.setAt(index, suggestion);
} else {
// If the suggestions manager gives us an identical item, it means that only the hit highlighted text has changed.
var existingElement = this._repeater.elementFromIndex(index);
if (utilities.hasClass(existingElement, ClassName.searchBoxSuggestionQuery)) {
this._addHitHighlightedText(existingElement, suggestion, suggestion.text);
}
else {
var resultSuggestionDiv = existingElement.querySelector("." + ClassName.searchBoxSuggestionResultText);
if (resultSuggestionDiv) {
this._addHitHighlightedText(resultSuggestionDiv, suggestion, suggestion.text);
var resultSuggestionDetailDiv = existingElement.querySelector("." + ClassName.searchBoxSuggestionResultDetailedText);
if (resultSuggestionDetailDiv) {
this._addHitHighlightedText(resultSuggestionDetailDiv, suggestion, suggestion.detailText);
}
}
}
}
}
if (document.activeElement === this._inputElement) {
this._updateFakeFocus();
}
},
_suggestionsRequestedHandler: function SearchBox_suggestionsRequestedHandler(event) {
// get the most up to date value of the input langauge from WinRT if available
if ((window.Windows) && (Windows.Globalization) && (Windows.Globalization.Language)) {
this._lastKeyPressLanguage = Windows.Globalization.Language.currentInputMethodLanguageTag;
}
var suggestionsRequestedEventDetail = event;
var deferral;
this._fireEvent(WinJS.UI.SearchBox._EventName.suggestionsrequested, {
setPromise: function (promise) {
deferral = suggestionsRequestedEventDetail.request.getDeferral();
promise.then(function () {
deferral.complete();
});
},
searchSuggestionCollection: suggestionsRequestedEventDetail.request.searchSuggestionCollection,
language: this._lastKeyPressLanguage,
linguisticDetails: this._getLinguisticDetails(true /*useCache*/, true /*createFilled*/),
queryText: this._inputElement.value
});
},
_fireEvent: function SearchBox_fireEvent(type, detail) {
// Returns true if ev.preventDefault() was not called
var event = document.createEvent("CustomEvent");
event.initCustomEvent(type, true, true, detail);
return this.element.dispatchEvent(event);
},
_requestingFocusOnKeyboardInputHandler: function SearchBox_requestingFocusOnKeyboardInputHandler(event) {
this._fireEvent(WinJS.UI.SearchBox._EventName.receivingfocusonkeyboardinput, null);
if (document.activeElement !== this._inputElement) {
try {
this._inputElement.focus();
} catch (e) {
}
}
},
_hasLinguisticDetailsChanged: function SearchBox_hasLinguisticDetailsChanged(newLinguisticDetails) {
var hasLinguisticDetailsChanged = false;
if ((this._prevLinguisticDetails.queryTextCompositionStart !== newLinguisticDetails.queryTextCompositionStart) ||
(this._prevLinguisticDetails.queryTextCompositionLength !== newLinguisticDetails.queryTextCompositionLength) ||
(this._prevLinguisticDetails.queryTextAlternatives.length !== newLinguisticDetails.queryTextAlternatives.length)) {
hasLinguisticDetailsChanged = true;
}
this._prevLinguisticDetails = newLinguisticDetails;
return hasLinguisticDetailsChanged;
},
_isProcessingImeFocusLossKey: function SearchBox_isProcessingImeFocusLossKey() {
return this._isProcessingDownKey || this._isProcessingUpKey || this._isProcessingTabKey || this._isProcessingEnterKey;
},
_updateQueryTextWithSuggestionText: function SearchBox_updateQueryTextWithSuggestionText(suggestionIndex) {
if ((suggestionIndex >= 0) && (suggestionIndex < this._suggestionsData.length)) {
this.queryText = this._suggestionsData.getAt(suggestionIndex).text;
}
}
}, {
_EventName: {
querychanged: EventName.querychanged,
querysubmitted: EventName.querysubmitted,
resultsuggestionchosen: EventName.resultsuggestionchosen,
suggestionsrequested: EventName.suggestionsrequested,
receivingfocusonkeyboardinput: EventName.receivingfocusonkeyboardinput
},
_Constants: {
MIN_POPUP_HEIGHT: 152,
IME_HANDLED_KEYCODE: 229
},
_getKeyModifiers: function SearchBox_getKeyModifiers(ev) {
// Returns the same value as http://msdn.microsoft.com/en-us/library/windows/apps/xaml/windows.system.virtualkeymodifiers
var VirtualKeys = {
ctrlKey: 1,
altKey: 2,
shiftKey: 4
};
var keyModifiers = 0;
if (ev.ctrlKey) {
keyModifiers |= VirtualKeys.ctrlKey;
}
if (ev.altKey) {
keyModifiers |= VirtualKeys.altKey;
}
if (ev.shiftKey) {
keyModifiers |= VirtualKeys.shiftKey;
}
return keyModifiers;
},
_sortAndMergeHits: function searchBox_sortAndMergeHits(hitsProvided) {
var reducedHits = [];
if (hitsProvided) {
// Copy hitsprovided array as winrt objects are immutable.
var hits = new Array(hitsProvided.length);
for (var i = 0; i < hitsProvided.length; i++) {
hits.push({ startPosition: hitsProvided[i].startPosition, length: hitsProvided[i].length });
}
hits.sort(WinJS.UI.SearchBox._hitStartPositionAscendingSorter);
hits.reduce(WinJS.UI.SearchBox._hitIntersectionReducer, reducedHits);
}
return reducedHits;
},
_hitStartPositionAscendingSorter: function searchBox_hitStartPositionAscendingSorter(firstHit, secondHit) {
var returnValue = 0;
if (firstHit.startPosition < secondHit.startPosition) {
returnValue = -1;
} else if (firstHit.startPosition > secondHit.startPosition) {
returnValue = 1;
}
return returnValue;
},
_hitIntersectionReducer: function searchBox_hitIntersectionReducer(reducedHits, nextHit, currentIndex, originalList) {
if (currentIndex === 0) {
reducedHits.push(nextHit);
} else {
var curHit = reducedHits[reducedHits.length - 1];
var curHitEndPosition = curHit.startPosition + curHit.length;
//#DBG _ASSERT(nextHit.startPosition >= curHit.startPosition);
if (nextHit.startPosition <= curHitEndPosition) {
// The next hit intersects or is next to current hit. Merge it.
var nextHitEndPosition = nextHit.startPosition + nextHit.length;
if (nextHitEndPosition > curHitEndPosition) {
curHit.length = nextHitEndPosition - curHit.startPosition;
}
} else {
// No intersection, simply add to reduced list.
reducedHits.push(nextHit);
}
}
return reducedHits;
},
_isTypeToSearchKey: function searchBox__isTypeToSearchKey(event) {
if (event.shiftKey || event.ctrlKey || event.altKey) {
return false;
}
return true;
}
});
WinJS.Class.mix(SearchBox, WinJS.UI.DOMEventMixin);
return SearchBox;
})
});
})(this, WinJS);
/// appbar,Flyout,Flyouts,registeredforsettings,SettingsFlyout,Statics,Syriac
(function settingsFlyoutInit(WinJS) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Provides users with fast, in-context access to settings that affect the current app.
///
///
/// Settings Flyout
///
///
///
///
///
///
Custom Settings
///
///
/// {Your Content Here}
///
/// ]]>
/// Raised just before showing a SettingsFlyout.
/// Raised immediately after a SettingsFlyout is fully shown.
/// Raised just before hiding a SettingsFlyout.
/// Raised immediately after a SettingsFlyout is fully hidden.
/// The SettingsFlyout control itself.
///
///
///
SettingsFlyout: WinJS.Namespace._lazy(function () {
var thisWinUI = WinJS.UI;
var focusHasHitSettingsPaneOnce;
// Class Names
var settingsFlyoutClass = "win-settingsflyout",
fullSettingsFlyoutClassName = "." + settingsFlyoutClass,
settingsFlyoutLightClass = "win-ui-light",
narrowClass = "win-narrow",
wideClass = "win-wide";
var firstDivClass = "win-firstdiv";
var finalDivClass = "win-finaldiv";
// Constants for width
var settingsNarrow = "narrow",
settingsWide = "wide";
// Determine if the settings pane (system language) is RTL or not.
function _shouldAnimateFromLeft() {
if (WinJS.Utilities.hasWinRT && Windows.UI.ApplicationSettings.SettingsEdgeLocation) {
var appSettings = Windows.UI.ApplicationSettings;
return (appSettings.SettingsPane.edge === appSettings.SettingsEdgeLocation.left);
} else {
return false;
}
};
// Get the settings control by matching the settingsCommandId
// if no match we'll try to match element id
function _getChildSettingsControl(parentElement, id) {
var settingElements = parentElement.querySelectorAll(fullSettingsFlyoutClassName);
var retValue,
control;
for (var i = 0; i < settingElements.length; i++) {
control = settingElements[i].winControl;
if (control) {
if (control.settingsCommandId === id) {
retValue = control;
break;
}
if (settingElements[i].id === id) {
retValue = retValue || control;
}
}
}
return retValue;
}
var SettingsFlyout = WinJS.Class.derive(WinJS.UI._Overlay, function SettingsFlyout_ctor(element, options) {
///
/// Creates a new SettingsFlyout control.
///
/// The DOM element that will host the control.
///
///
/// The set of properties and values to apply to the new SettingsFlyout.
///
/// The new SettingsFlyout control.
///
///
// Make sure there's an input element
this._element = element || document.createElement("div");
this._id = this._element.id || this._element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
// Call the base overlay constructor helper
this._baseOverlayConstructor(this._element, options);
this._addFirstDiv();
this._addFinalDiv();
// Handle "esc" & "tab" key presses
this._element.addEventListener("keydown", this._handleKeyDown, true);
// Make a click eating div
thisWinUI._Overlay._createClickEatingDivAppBar();
// Start settings hidden
this._element.style.visibilty = "hidden";
this._element.style.display = "none";
// Attach our css class
WinJS.Utilities.addClass(this._element, settingsFlyoutClass);
// apply the light theme styling to the win-content elements inside the SettingsFlyout
WinJS.Utilities.query("div.win-content", this._element).
forEach(function (e) {
if (!e.msMatchesSelector('.win-ui-dark, .win-ui-dark *')) {
WinJS.Utilities.addClass(e, settingsFlyoutLightClass);
}
});
// Make sure we have an ARIA role
var role = this._element.getAttribute("role");
if (role === null || role === "" || role === undefined) {
this._element.setAttribute("role", "dialog");
}
var label = this._element.getAttribute("aria-label");
if (label === null || label === "" || label === undefined) {
this._element.setAttribute("aria-label", strings.ariaLabel);
}
// Make sure _Overlay event handlers are hooked up
this._addOverlayEventHandlers(true);
// Make sure animations are hooked up
this._currentAnimateIn = this._animateSlideIn;
this._currentAnimateOut = this._animateSlideOut;
this._writeProfilerMark("constructor,StopTM");
}, {
// Public Properties
///
/// Width of the SettingsFlyout, "narrow", or "wide".
///
/// SettingsFlyout.width may be altered or unavailable in future versions. Instead, style the CSS width property on elements with the .win-settingsflyout class.
///
///
///
width: {
get: function () {
return this._width;
},
set: function (value) {
WinJS.Utilities._deprecated(strings.widthDeprecationMessage);
if (value === this._width) {
return;
}
// Get rid of old class
if (this._width === settingsNarrow) {
WinJS.Utilities.removeClass(this._element, narrowClass);
} else if (this._width === settingsWide) {
WinJS.Utilities.removeClass(this._element, wideClass);
}
this._width = value;
// Attach our new css class
if (this._width === settingsNarrow) {
WinJS.Utilities.addClass(this._element, narrowClass);
} else if (this._width === settingsWide) {
WinJS.Utilities.addClass(this._element, wideClass);
}
}
},
///
/// Define the settings command Id for the SettingsFlyout control.
///
///
settingsCommandId: {
get: function () {
return this._settingsCommandId;
},
set: function (value) {
this._settingsCommandId = value;
}
},
show: function () {
///
///
/// Shows the SettingsFlyout, if hidden.
///
///
///
// Just call private version to make appbar flags happy
// Don't do anything if disabled
if (this.disabled) {
return;
}
this._writeProfilerMark("show,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndShow().
this._show();
},
_dispose: function SettingsFlyout_dispose() {
WinJS.Utilities.disposeSubTree(this.element);
this._dismiss();
},
_show: function SettingsFlyout_show() {
// We call our base "_baseShow" because SettingsFlyout overrides show
this._baseShow();
// Need click-eating div to be visible,
// (even if now hiding, we'll show and need click eater)
thisWinUI._Overlay._showClickEatingDivAppBar();
},
_endShow: function SettingsFlyout_endShow() {
// Clean up after showing
this._initAfterAnimation();
},
_initAfterAnimation: function SettingsFlyout_initAfterAnimation() {
focusHasHitSettingsPaneOnce = 0;
// Verify that the firstDiv and finalDiv are in the correct location.
// Move them to the correct location or add them if they are not.
if (!WinJS.Utilities.hasClass(this.element.children[0], firstDivClass)) {
var firstDiv = this.element.querySelectorAll(".win-first");
if (firstDiv && firstDiv.length > 0) {
firstDiv.item(0).parentNode.removeChild(firstDiv.item(0));
}
this._addFirstDiv();
}
// Set focus to the firstDiv
if (this.element.children[0]) {
this.element.children[0].addEventListener("focusout", function () { focusHasHitSettingsPaneOnce = 1; }, false);
this.element.children[0].focus();
}
if (!WinJS.Utilities.hasClass(this.element.children[this.element.children.length - 1], finalDivClass)) {
var finalDiv = this.element.querySelectorAll(".win-final");
if (finalDiv && finalDiv.length > 0) {
finalDiv.item(0).parentNode.removeChild(finalDiv.item(0));
}
this._addFinalDiv();
}
this._setBackButtonsAriaLabel();
},
_setBackButtonsAriaLabel: function SettingsFlyout_setBackButtonsAriaLabel() {
var backbuttons = this.element.querySelectorAll(".win-backbutton");
var label;
for (var i = 0; i < backbuttons.length; i++) {
label = backbuttons[i].getAttribute("aria-label");
if (label === null || label === "" || label === undefined) {
backbuttons[i].setAttribute("aria-label", strings.backbuttonAriaLabel);
}
}
},
hide: function () {
///
///
/// Hides the SettingsFlyout, if visible, regardless of other state.
///
///
///
// Just call private version to make appbar flags happy
this._writeProfilerMark("hide,StartTM"); // The corresponding "stop" profiler mark is handled in _Overlay._baseEndHide().
this._hide();
},
_hide: function SettingsFlyout_hide() {
if (this._baseHide()) {
// Need click-eating div to be hidden
thisWinUI._Overlay._hideClickEatingDivAppBar();
}
},
// SettingsFlyout animations
_animateSlideIn: function SettingsFlyout_animateSlideIn() {
var animateFromLeft = _shouldAnimateFromLeft();
var offset = animateFromLeft ? "-100px" : "100px";
WinJS.Utilities.query("div.win-content", this._element).
forEach(function (e) { WinJS.UI.Animation.enterPage(e, { left: offset }) });
var where,
width = this._element.offsetWidth;
// Slide in from right side or left side?
if (animateFromLeft) {
// RTL
where = { top: "0px", left: "-" + width + "px" };
this._element.style.right = "auto";
this._element.style.left = "0px";
} else {
// From right side
where = { top: "0px", left: width + "px" };
this._element.style.right = "0px";
this._element.style.left = "auto";
}
this._element.style.opacity = 1;
this._element.style.visibility = "visible";
return WinJS.UI.Animation.showPanel(this._element, where);
},
_animateSlideOut: function SettingsFlyout_animateSlideOut() {
var where,
width = this._element.offsetWidth;
if (_shouldAnimateFromLeft()) {
// RTL
where = { top: "0px", left: width + "px" };
this._element.style.right = "auto";
this._element.style.left = "-" + width + "px";
} else {
// From right side
where = { top: "0px", left: "-" + width + "px" };
this._element.style.right = "-" + width + "px";
this._element.style.left = "auto";
}
return WinJS.UI.Animation.showPanel(this._element, where);
},
_fragmentDiv: {
get: function SettingsFlyout_fragmentDiv_get() {
return this._fragDiv;
},
set: function SettingsFlyout_fragmentDiv_set(value) {
this._fragDiv = value;
}
},
_unloadPage: function SettingsFlyout_unloadPage(event) {
var settingsControl = event.currentTarget.winControl;
settingsControl.removeEventListener(thisWinUI._Overlay.afterHide, this._unloadPage, false);
WinJS.Promise.as().then(function () {
if (settingsControl._fragmentDiv) {
document.body.removeChild(settingsControl._fragmentDiv);
settingsControl._fragmentDiv = null;
}
});
},
_dismiss: function SettingsFlyout_dismiss() {
this.addEventListener(thisWinUI._Overlay.afterHide, this._unloadPage, false);
this._hide();
},
_handleKeyDown: function SettingsFlyout_handleKeyDown(event) {
if (event.key === "Esc") {
event.preventDefault();
event.stopPropagation();
this.winControl._dismiss();
} else if ((event.key === "Spacebar" || event.key === "Enter")
&& (this.children[0] === document.activeElement)) {
event.preventDefault();
event.stopPropagation();
this.winControl._dismiss();
} else if (event.shiftKey && event.key === "Tab"
&& this.children[0] === document.activeElement) {
event.preventDefault();
event.stopPropagation();
var _elms = this.getElementsByTagName("*");
for (var i = _elms.length - 2; i >= 0; i--) {
_elms[i].focus();
if (_elms[i] === document.activeElement) {
break;
}
}
}
},
_focusOnLastFocusableElementFromParent: function SettingsFlyout_focusOnLastFocusableElementFromParent() {
var active = document.activeElement;
if (!focusHasHitSettingsPaneOnce || !active || !WinJS.Utilities.hasClass(active, firstDivClass)) {
return;
}
var _elms = this.parentElement.getElementsByTagName("*");
// There should be at least 1 element in addition to the firstDiv & finalDiv
if (_elms.length <= 2) {
return;
}
// Get the tabIndex set to the finalDiv (which is the highest)
var _highestTabIndex = _elms[_elms.length - 1].tabIndex;
// If there are positive tabIndices, set focus to the element with the highest tabIndex.
// Otherwise set focus to the last focusable element in DOM order.
if (_highestTabIndex) {
for (var i = _elms.length - 2; i > 0; i--) {
if (_elms[i].tabIndex === _highestTabIndex) {
_elms[i].focus();
break;
}
}
} else {
for (i = _elms.length - 2; i > 0; i--) {
// Skip
with undefined tabIndex (To work around Win8 bug #622245)
if ((_elms[i].tagName !== "DIV") || (_elms[i].getAttribute("tabIndex") !== null)) {
_elms[i].focus();
if (_elms[i] === document.activeElement) {
break;
}
}
}
}
},
_focusOnFirstFocusableElementFromParent: function SettingsFlyout_focusOnFirstFocusableElementFromParent() {
var active = document.activeElement;
if (!active || !WinJS.Utilities.hasClass(active, finalDivClass)) {
return;
}
var _elms = this.parentElement.getElementsByTagName("*");
// There should be at least 1 element in addition to the firstDiv & finalDiv
if (_elms.length <= 2) {
return;
}
// Get the tabIndex set to the firstDiv (which is the lowest)
var _lowestTabIndex = _elms[0].tabIndex;
// If there are positive tabIndices, set focus to the element with the lowest tabIndex.
// Otherwise set focus to the first focusable element in DOM order.
if (_lowestTabIndex) {
for (var i = 1; i < _elms.length - 1; i++) {
if (_elms[i].tabIndex === _lowestTabIndex) {
_elms[i].focus();
break;
}
}
} else {
for (i = 1; i < _elms.length - 1; i++) {
// Skip
with undefined tabIndex (To work around Win8 bug #622245)
if ((_elms[i].tagName !== "DIV") || (_elms[i].getAttribute("tabIndex") !== null)) {
_elms[i].focus();
if (_elms[i] === document.activeElement) {
break;
}
}
}
}
},
// Create and add a new first div to the beginning of the list
_addFirstDiv: function SettingsFlyout_addFirstDiv() {
var _elms = this._element.getElementsByTagName("*");
var _minTab = 0;
for (var i = 0; i < _elms.length; i++) {
if ((0 < _elms[i].tabIndex) && (_minTab === 0 || _elms[i].tabIndex < _minTab)) {
_minTab = _elms[i].tabIndex;
}
}
var firstDiv = document.createElement("div");
firstDiv.className = firstDivClass;
firstDiv.style.display = "inline";
firstDiv.setAttribute("role", "menuitem");
firstDiv.setAttribute("aria-hidden", "true");
firstDiv.tabIndex = _minTab;
firstDiv.addEventListener("focus", this._focusOnLastFocusableElementFromParent, false);
this._element.insertAdjacentElement("AfterBegin", firstDiv);
},
// Create and add a new final div to the end of the list
_addFinalDiv: function SettingsFlyout_addFinalDiv() {
var _elms = this._element.getElementsByTagName("*");
var _maxTab = 0;
for (var i = 0; i < _elms.length; i++) {
if (_elms[i].tabIndex > _maxTab) {
_maxTab = _elms[i].tabIndex;
}
}
var finalDiv = document.createElement("div");
finalDiv.className = finalDivClass;
finalDiv.style.display = "inline";
finalDiv.setAttribute("role", "menuitem");
finalDiv.setAttribute("aria-hidden", "true");
finalDiv.tabIndex = _maxTab;
finalDiv.addEventListener("focus", this._focusOnFirstFocusableElementFromParent, false);
this._element.appendChild(finalDiv);
},
_writeProfilerMark: function SettingsFlyout_writeProfilerMark(text) {
msWriteProfilerMark("WinJS.UI.SettingsFlyout:" + this._id + ":" + text);
}
});
// Statics
SettingsFlyout.show = function () {
///
///
/// Shows the SettingsPane UI, if hidden, regardless of other states.
///
///
///
if (WinJS.Utilities.hasWinRT) {
Windows.UI.ApplicationSettings.SettingsPane.show();
}
// And hide the WWA one
var elements = document.querySelectorAll('div[data-win-control="WinJS.UI.SettingsFlyout"]');
var len = elements.length;
for (var i = 0; i < len; i++) {
var settingsFlyout = elements[i].winControl;
if (settingsFlyout) {
settingsFlyout._dismiss();
}
}
};
var _settingsEvent = { event: undefined };
SettingsFlyout.populateSettings = function (e) {
///
///
/// Loads a portion of the SettingsFlyout. Your app calls this when the user invokes a settings command and the WinJS.Application.onsettings event occurs.
///
///
/// An object that contains information about the event, received from the WinJS.Application.onsettings event. The detail property of this object contains
/// the applicationcommands sub-property that you set to an array of settings commands.
///
///
///
_settingsEvent.event = e.detail;
if (_settingsEvent.event.applicationcommands) {
var n = Windows.UI.ApplicationSettings;
Object.keys(_settingsEvent.event.applicationcommands).forEach(function (name) {
var setting = _settingsEvent.event.applicationcommands[name];
if (!setting.title) { setting.title = name; }
var command = new n.SettingsCommand(name, setting.title, thisWinUI.SettingsFlyout._onSettingsCommand);
_settingsEvent.event.e.request.applicationCommands.append(command);
});
}
};
SettingsFlyout._onSettingsCommand = function (command) {
var id = command.id;
if (_settingsEvent.event.applicationcommands && _settingsEvent.event.applicationcommands[id]) {
thisWinUI.SettingsFlyout.showSettings(id, _settingsEvent.event.applicationcommands[id].href);
}
};
SettingsFlyout.showSettings = function (id, path) {
///
///
/// Show the SettingsFlyout using the settings element identifier (ID) and the path of the page that contains the settings element.
///
///
/// The ID of the settings element.
///
///
/// The path of the page that contains the settings element.
///
///
///
var control = _getChildSettingsControl(document, id);
if (control) {
control.show();
} else if (path) {
var divElement = document.createElement("div");
divElement = document.body.appendChild(divElement);
WinJS.UI.Pages.render(path, divElement).then(function () {
control = _getChildSettingsControl(divElement, id);
if (control) {
control._fragmentDiv = divElement;
control.show();
} else {
document.body.removeChild(divElement);
}
});
} else {
throw new WinJS.ErrorFromName("WinJS.UI.SettingsFlyout.BadReference", strings.badReference);
}
};
var strings = {
get ariaLabel() { return WinJS.Resources._getWinJSString("ui/settingsFlyoutAriaLabel").value; },
get badReference() { return WinJS.Resources._getWinJSString("ui/badReference").value; },
get backbuttonAriaLabel() { return WinJS.Resources._getWinJSString("ui/backbuttonarialabel").value; },
get widthDeprecationMessage() { return WinJS.Resources._getWinJSString("ui/settingsFlyoutWidthIsDeprecated").value; },
};
return SettingsFlyout;
})
});
})(WinJS);
(function itemContainerInit(global, WinJS, undefined) {
"use strict";
var utilities = WinJS.Utilities;
var createEvent = utilities._createEventProperty;
var eventNames = {
invoked: "invoked",
selectionchanging: "selectionchanging",
selectionchanged: "selectionchanged"
};
WinJS.Namespace.define("WinJS.UI", {
///
/// Defines an item that can be pressed, swiped, and dragged.
///
///
///
/// HTML content
/// ]]>
/// Raised when the user taps or clicks the item.
/// Raised before the item is selected or deselected.
/// Raised after the item is selected or deselected.
/// Main container for the selection item control.
/// The background of a selection checkmark.
/// A selection checkmark.
/// Used to display an outline when the main container has keyboard focus.
///
///
///
ItemContainer: WinJS.Namespace._lazy(function () {
var strings = {
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; }
};
var ItemContainer = WinJS.Class.define(function ItemContainer_ctor(element, options) {
///
///
/// Creates a new ItemContainer control.
///
///
/// The DOM element that hosts the ItemContainer control.
///
///
/// An object that contains one or more property/value pairs to apply to the new control.
/// Each property of the options object corresponds to one of the control's properties or events.
/// Event names must begin with "on". For example, to provide a handler for the selectionchanging event,
/// add a property named "onselectionchanging" to the options object and set its value to the event handler.
///
///
/// The new ItemContainer control.
///
///
element = element || document.createElement("DIV");
this._id = element.id || element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
options = options || {};
if (element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.ItemContainer.DuplicateConstruction", strings.duplicateConstruction);
}
// Attaching JS control to DOM element
element.winControl = this;
this._element = element;
WinJS.Utilities.addClass(element, "win-disposable");
this._selectionMode = WinJS.UI.SelectionMode.single;
this._draggable = false;
this._pressedEntity = { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX };
this.tapBehavior = WinJS.UI.TapBehavior.invokeOnly;
this.swipeOrientation = WinJS.UI.Orientation.vertical;
this.swipeBehavior = WinJS.UI.SwipeBehavior.select;
WinJS.Utilities.addClass(this.element, WinJS.UI.ItemContainer._ClassName.itemContainer + " " + WinJS.UI._containerClass);
this._setupInternalTree();
this._selection = new WinJS.UI._SingleItemSelectionManager(element, this._itemBox);
this._setTabIndex();
WinJS.UI.setOptions(this, options);
this._mutationObserver = new MutationObserver(this._itemPropertyChange.bind(this));
this._mutationObserver.observe(element, { attributes: true, attributeFilter: ["aria-selected"] });
this._setAriaRole();
var that = this;
if (!this.selectionDisabled) {
setImmediate(function () {
that._setDirectionClass();
});
}
this._itemEventsHandler = new WinJS.UI._ItemEventsHandler(Object.create({
containerFromElement: function (element) {
return that.element;
},
indexForItemElement: function (element) {
return that.element.uniqueID;
},
indexForHeaderElement: function () {
return WinJS.UI._INVALID_INDEX;
},
itemBoxAtIndex: function (index) {
return that._itemBox;
},
itemAtIndex: function (index) {
return that.element;
},
headerAtIndex: function (index) {
return null;
},
containerAtIndex: function (index) {
return that.element;
},
isZombie: function () {
return this._disposed;
},
getItemPosition: function (index) {
return that._getItemPosition();
},
rtl: function () {
return that._rtl();
},
fireInvokeEvent: function (itemIndex, itemElement) {
that._fireInvokeEvent();
},
verifySelectionAllowed: function (index) {
return that._verifySelectionAllowed();
},
changeFocus: function (newFocus, skipSelection, ctrlKeyDown, skipEnsureVisible, keyboardFocused) { },
selectRange: function (firstIndex, lastIndex, additive) {
return that._selection.set({ firstIndex: firstIndex, lastIndex: lastIndex });
}
}, {
pressedEntity: {
get: function () {
return that._pressedEntity;
},
set: function (value) {
that._pressedEntity = value;
}
},
pressedElement: {
enumerable: true,
set: function (value) {
that._pressedElement = value;
}
},
eventHandlerRoot: {
enumerable: true,
get: function () {
return that.element;
}
},
swipeBehavior: {
enumerable: true,
get: function () {
return that._swipeBehavior;
}
},
selectionMode: {
enumerable: true,
get: function () {
return that._selectionMode;
}
},
accessibleItemClass: {
enumerable: true,
get: function () {
// CSS class of the element with the aria role
return WinJS.UI._containerClass;
}
},
canvasProxy: {
enumerable: true,
get: function () {
return that._captureProxy;
}
},
tapBehavior: {
enumerable: true,
get: function () {
return that._tapBehavior;
}
},
draggable: {
enumerable: true,
get: function () {
return that._draggable;
}
},
selection: {
enumerable: true,
get: function () {
return that._selection;
}
},
horizontal: {
enumerable: true,
get: function () {
return that._swipeOrientation === WinJS.UI.Orientation.vertical;
}
},
customFootprintParent: {
enumerable: true,
get: function () {
// Use the main container as the footprint
return null;
}
},
skipPreventDefaultOnPointerDown: {
enumerable: true,
get: function () {
return true;
}
}
}));
function eventHandler(eventName, caseSensitive, capture) {
return {
name: (caseSensitive ? eventName : eventName.toLowerCase()),
handler: function (eventObject) {
that["_on" + eventName](eventObject);
},
capture: capture
};
}
var events = [
eventHandler("MSManipulationStateChanged", true, true),
eventHandler("PointerDown"),
eventHandler("Click"),
eventHandler("PointerUp"),
eventHandler("PointerCancel"),
eventHandler("LostPointerCapture"),
eventHandler("ContextMenu"),
eventHandler("MSHoldVisual", true),
eventHandler("Focus"),
eventHandler("Blur"),
eventHandler("DragStart"),
eventHandler("DragEnd"),
eventHandler("KeyDown")
];
events.forEach(function (eventHandler) {
that.element.addEventListener(eventHandler.name, eventHandler.handler, !!eventHandler.capture);
});
this._writeProfilerMark("constructor,StopTM");
}, {
///
/// Gets the DOM element that hosts the itemContainer control.
///
element: {
get: function () {
return this._element;
}
},
///
/// Gets or sets a value that specifies whether the item can be dragged. The default value is false.
///
///
draggable: {
get: function () {
return this._draggable;
},
set: function (value) {
if (this._draggable !== value) {
this._draggable = value;
this._updateDraggableAttribute();
}
}
},
///
/// Gets or sets a value that specifies whether the item is selected.
///
selected: {
get: function () {
return this._selection.selected;
},
set: function (value) {
if (this._selection.selected !== value) {
this._selection.selected = value;
}
}
},
///
/// Gets or sets the swipe orientation of the ItemContainer control.
/// The default value is "vertical".
///
swipeOrientation: {
get: function () {
return this._swipeOrientation;
},
set: function (value) {
if (value === WinJS.UI.Orientation.vertical) {
WinJS.Utilities.removeClass(this.element, WinJS.UI.ItemContainer._ClassName.horizontal);
WinJS.Utilities.addClass(this.element, WinJS.UI.ItemContainer._ClassName.vertical);
} else {
value = WinJS.UI.Orientation.horizontal;
WinJS.Utilities.removeClass(this.element, WinJS.UI.ItemContainer._ClassName.vertical);
WinJS.Utilities.addClass(this.element, WinJS.UI.ItemContainer._ClassName.horizontal);
}
this._swipeOrientation = value;
}
},
///
/// Gets or sets how the ItemContainer control reacts when the user taps or clicks an item.
/// The tap or click can invoke the item, select it and invoke it, or have no effect.
/// Possible values: "toggleSelect", "invokeOnly", and "none". The default value is "invokeOnly".
///
tapBehavior: {
get: function () {
return this._tapBehavior;
},
set: function (value) {
this._tapBehavior = value;
this._setAriaRole();
}
},
///
/// Gets or sets how the ItemContainer control reacts to the swipe interaction.
/// The swipe gesture can select the item or it can have no effect on the current selection.
/// Possible values: "select", "none". The default value is: "select".
///
///
swipeBehavior: {
get: function () {
return this._swipeBehavior;
},
set: function (value) {
this._swipeBehavior = value;
this._setSwipeClass();
}
},
///
/// Gets or sets whether the item selection is disabled. The default value is false.
///
selectionDisabled: {
get: function () {
return this._selectionMode === WinJS.UI.SelectionMode.none;
},
set: function (value) {
if (value) {
this._selectionMode = WinJS.UI.SelectionMode.none;
} else {
this._setDirectionClass();
this._selectionMode = WinJS.UI.SelectionMode.single;
}
this._setSwipeClass();
this._setAriaRole();
}
},
///
/// Raised when the item is invoked. You can use the tapBehavior property to specify whether taps and clicks invoke the item.
///
oninvoked: createEvent(eventNames.invoked),
///
/// Raised just before the item is selected or deselected.
///
onselectionchanging: createEvent(eventNames.selectionchanging),
///
/// Raised after the item is selected or deselected.
///
onselectionchanged: createEvent(eventNames.selectionchanged),
forceLayout: function () {
///
///
/// Forces the ItemContainer control to update its layout.
/// Use this function when the reading direction of the app changes after the control has been initialized.
///
///
this._forceLayout();
},
dispose: function () {
///
///
/// Disposes this control.
///
///
if (this._disposed) {
return;
}
this._disposed = true;
this._itemEventsHandler.dispose();
WinJS.Utilities.disposeSubTree(this.element);
},
_onMSManipulationStateChanged: function ItemContainer_onMSManipulationStateChanged(eventObject) {
this._itemEventsHandler.onMSManipulationStateChanged(eventObject);
},
_onPointerDown: function ItemContainer_onPointerDown(eventObject) {
this._itemEventsHandler.onPointerDown(eventObject);
},
_onClick: function ItemContainer_onClick(eventObject) {
this._itemEventsHandler.onClick(eventObject);
},
_onPointerUp: function ItemContainer_onPointerUp(eventObject) {
if (utilities.hasClass(this._itemBox, WinJS.UI._itemFocusClass)) {
this._onBlur(eventObject);
}
this._itemEventsHandler.onPointerUp(eventObject);
},
_onPointerCancel: function ItemContainer_onPointerCancel(eventObject) {
this._itemEventsHandler.onPointerCancel(eventObject);
},
_onLostPointerCapture: function ItemContainer_onLostPointerCapture(eventObject) {
this._itemEventsHandler.onLostPointerCapture(eventObject);
},
_onContextMenu: function ItemContainer_onContextMenu(eventObject) {
this._itemEventsHandler.onContextMenu(eventObject);
},
_onMSHoldVisual: function ItemContainer_onMSHoldVisual(eventObject) {
this._itemEventsHandler.onMSHoldVisual(eventObject);
},
_onFocus: function ItemContainer_onFocus(eventObject) {
if (this._itemBox.querySelector("." + WinJS.UI._itemFocusOutlineClass) || !WinJS.UI._keyboardSeenLast) {
return;
}
utilities.addClass(this._itemBox, WinJS.UI._itemFocusClass);
var outline = document.createElement("div");
outline.className = WinJS.UI._itemFocusOutlineClass;
this._itemBox.appendChild(outline);
},
_onBlur: function ItemContainer_onBlur(eventObject) {
utilities.removeClass(this._itemBox, WinJS.UI._itemFocusClass);
var outline = this._itemBox.querySelector("." + WinJS.UI._itemFocusOutlineClass);
if (outline) {
outline.parentNode.removeChild(outline);
}
},
_onDragStart: function ItemContainer_onDragStart(eventObject) {
// Drag shouldn't be initiated when the user holds down the mouse on a win-interactive element and moves.
// The problem is that the dragstart event's srcElement+target will both be an itembox (which has draggable=true), so we can't check for win-interactive in the dragstart event handler.
// The itemEventsHandler sets our _pressedElement field on PointerDown, so we use that instead when checking for interactive.
if (this._pressedElement && this._itemEventsHandler._isInteractive(this._pressedElement)) {
eventObject.preventDefault();
} else {
this._dragging = true;
var that = this;
// We delay setting the win-dragsource CSS class so that IE has time to create a thumbnail before me make it opaque
setImmediate(function () {
if (that._dragging) {
utilities.addClass(that._itemBox, WinJS.UI._dragSourceClass);
}
});
}
},
_onDragEnd: function ItemContainer_onDragEnd(eventObject) {
this._dragging = false;
utilities.removeClass(this._itemBox, WinJS.UI._dragSourceClass);
this._itemEventsHandler.resetPointerDownState();
},
_onKeyDown: function ItemContainer_onKeyDown(eventObject) {
if (!this._itemEventsHandler._isInteractive(eventObject.srcElement)) {
var Key = utilities.Key,
keyCode = eventObject.keyCode,
swipeEnabled = this._swipeBehavior === WinJS.UI.SwipeBehavior.select;
var handled = false;
if (!eventObject.ctrlKey && keyCode === Key.enter) {
var allowed = this._verifySelectionAllowed();
if (allowed.canTapSelect) {
this.selected = !this.selected;
}
this._fireInvokeEvent();
handled = true;
} else if (eventObject.ctrlKey && keyCode === Key.enter ||
(swipeEnabled && eventObject.shiftKey && keyCode === Key.F10) ||
(swipeEnabled && keyCode === Key.menu) ||
keyCode === Key.space) {
if (!this.selectionDisabled) {
this.selected = !this.selected;
try {
this.element.setActive();
} catch (e) { }
handled = true;
}
} else if (keyCode === Key.escape && this.selected) {
this.selected = false;
handled = true;
}
if (handled) {
eventObject.stopPropagation();
eventObject.preventDefault();
}
}
},
_setTabIndex: function ItemContainer_setTabIndex() {
var currentTabIndex = this.element.getAttribute("tabindex");
if (!currentTabIndex) {
// Set the tabindex to 0 only if the application did not already
// provide a tabindex
this.element.setAttribute("tabindex", "0")
}
},
_rtl: function ItemContainer_rtl() {
if (typeof this._cachedRTL !== "boolean") {
this._cachedRTL = window.getComputedStyle(this.element, null).direction === "rtl";
}
return this._cachedRTL;
},
_setDirectionClass: function ItemContainer_setDirectionClass() {
utilities[this._rtl() ? "addClass" : "removeClass"](this.element, WinJS.UI._rtlListViewClass);
},
_forceLayout: function ItemContainer_forceLayout() {
this._cachedRTL = window.getComputedStyle(this.element, null).direction === "rtl";
this._setDirectionClass();
},
_getItemPosition: function ItemContainer_getItemPosition() {
var container = this.element;
if (container) {
return WinJS.Promise.wrap({
left: (this._rtl() ?
container.offsetParent.offsetWidth - container.offsetLeft - container.offsetWidth :
container.offsetLeft),
top: container.offsetTop,
totalWidth: utilities.getTotalWidth(container),
totalHeight: utilities.getTotalHeight(container),
contentWidth: utilities.getContentWidth(container),
contentHeight: utilities.getContentHeight(container)
});
} else {
return WinJS.Promise.cancel;
}
},
_itemPropertyChange: function ItemContainer_itemPropertyChange(list) {
if (this._disposed) { return; }
var container = list[0].target;
var ariaSelected = container.getAttribute("aria-selected") === "true";
// Only respond to aria-selected changes coming from UIA. This check
// relies on the fact that, in renderSelection, we update the selection
// visual before aria-selected.
if (ariaSelected !== WinJS.UI._isSelectionRendered(this._itemBox)) {
if (this.selectionDisabled) {
// Revert the change made by UIA since the control has selection disabled
WinJS.UI._setAttribute(container, "aria-selected", !ariaSelected);
} else {
this.selected = ariaSelected;
// Revert the change because the update was prevented on the selectionchanging event
if (ariaSelected !== this.selected) {
WinJS.UI._setAttribute(container, "aria-selected", !ariaSelected);
}
}
}
},
_setSwipeClass: function ItemContainer_setSwipeClass() {
// We apply an -ms-touch-action style to block panning and swiping from occurring at the same time.
if ((this._swipeBehavior === WinJS.UI.SwipeBehavior.select && this._selectionMode !== WinJS.UI.SelectionMode.none) || this._draggable) {
utilities.addClass(this._element, WinJS.UI._swipeableClass);
} else {
utilities.removeClass(this._element, WinJS.UI._swipeableClass);
}
},
_updateDraggableAttribute: function ItemContainer_setSwipeClass() {
this._setSwipeClass();
this._itemBox.setAttribute("draggable", this._draggable);
},
_verifySelectionAllowed: function ItemContainer_verifySelectionAllowed() {
if (this._selectionMode !== WinJS.UI.SelectionMode.none && (this._tapBehavior === WinJS.UI.TapBehavior.toggleSelect || this._swipeBehavior === WinJS.UI.SwipeBehavior.select)) {
var canSelect = this._selection.fireSelectionChanging();
return {
canSelect: canSelect,
canTapSelect: canSelect && this._tapBehavior === WinJS.UI.TapBehavior.toggleSelect
};
} else {
return {
canSelect: false,
canTapSelect: false
};
}
},
_setupInternalTree: function ItemContainer_setupInternalTree() {
var item = document.createElement("div");
item.className = WinJS.UI._itemClass;
this._captureProxy = document.createElement("div");
this._itemBox = document.createElement("div");
this._itemBox.className = WinJS.UI._itemBoxClass;
var child = this.element.firstChild;
while (child) {
var sibling = child.nextSibling;
item.appendChild(child);
child = sibling;
}
this.element.appendChild(this._itemBox);
this._itemBox.appendChild(item);
this.element.appendChild(this._captureProxy);
},
_fireInvokeEvent: function ItemContainer_fireInvokeEvent() {
if (this.tapBehavior !== WinJS.UI.TapBehavior.none) {
var eventObject = document.createEvent("CustomEvent");
eventObject.initCustomEvent(eventNames.invoked, true, false, {});
this.element.dispatchEvent(eventObject);
}
},
_setAriaRole: function ItemContainer_setAriaRole() {
if (!this.element.getAttribute("role") || this._usingDefaultItemRole) {
this._usingDefaultItemRole = true;
var defaultItemRole;
if (this.tapBehavior === WinJS.UI.TapBehavior.none && this.selectionDisabled) {
defaultItemRole = "listitem";
} else {
defaultItemRole = "option";
}
WinJS.UI._setAttribute(this.element, "role", defaultItemRole);
}
},
_writeProfilerMark: function ItemContainer_writeProfilerMark(text) {
var message = "WinJS.UI.ItemContainer:" + this._id + ":" + text;
msWriteProfilerMark(message);
WinJS.log && WinJS.log(message, null, "itemcontainerprofiler");
}
}, {
// Names of classes used by the ItemContainer.
_ClassName: {
itemContainer: "win-itemcontainer",
vertical: "win-vertical",
horizontal: "win-horizontal",
}
});
WinJS.Class.mix(ItemContainer, WinJS.UI.DOMEventMixin);
return ItemContainer;
}),
_SingleItemSelectionManager: WinJS.Namespace._lazy(function () {
return WinJS.Class.define(function SingleItemSelectionManager_ctor(element, itemBox) {
this._selected = false;
this._element = element;
this._itemBox = itemBox;
}, {
selected: {
get: function () {
return this._selected;
},
set: function (value) {
value = !!value;
if (this._selected !== value) {
if (this.fireSelectionChanging()) {
this._selected = value;
WinJS.UI._ItemEventsHandler.renderSelection(this._itemBox, this._element, value, true, this._element);
this.fireSelectionChanged();
}
}
}
},
count: function SingleItemSelectionManager_count() {
return this._selected ? 1 : 0;
},
getIndices: function SingleItemSelectionManager_getIndices() {
// not used
},
getItems: function SingleItemSelectionManager_getItems() {
// not used
},
getRanges: function SingleItemSelectionManager_getRanges() {
// not used
},
isEverything: function SingleItemSelectionManager_isEverything() {
return false;
},
set: function SingleItemSelectionManager_set(items) {
this.selected = true;
},
clear: function SingleItemSelectionManager_clear() {
this.selected = false;
},
add: function SingleItemSelectionManager_add(items) {
this.selected = true;
},
remove: function SingleItemSelectionManager_remove(items) {
this.selected = false;
},
selectAll: function SingleItemSelectionManager_selectAll() {
// not used
},
fireSelectionChanging: function SingleItemSelectionManager_fireSelectionChanging() {
var eventObject = document.createEvent("CustomEvent");
eventObject.initCustomEvent(eventNames.selectionchanging, true, true, {});
return this._element.dispatchEvent(eventObject);
},
fireSelectionChanged: function ItemContainer_fireSelectionChanged() {
var eventObject = document.createEvent("CustomEvent");
eventObject.initCustomEvent(eventNames.selectionchanged, true, false, {});
this._element.dispatchEvent(eventObject);
},
_isIncluded: function SingleItemSelectionManager_isIncluded(index) {
return this._selected;
},
_getFocused: function SingleItemSelectionManager_getFocused(index) {
return { type: WinJS.UI.ObjectType.item, index: WinJS.UI._INVALID_INDEX };
}
})
})
});
})(this, WinJS);
(function KeyboardBehaviorInit(global, WinJS, undefined) {
"use strict";
// not supported in WebWorker
if (!global.document) {
return;
}
WinJS.UI._keyboardSeenLast = false;
window.addEventListener("pointerdown", function (ev) {
if (WinJS.UI._keyboardSeenLast) {
WinJS.UI._keyboardSeenLast = false;
}
}, true);
window.addEventListener("keydown", function (ev) {
if (!WinJS.UI._keyboardSeenLast) {
WinJS.UI._keyboardSeenLast = true;
}
}, true);
WinJS.Namespace.define("WinJS.UI", {
_WinKeyboard: function (element) {
// Win Keyboard behavior is a solution that would be similar to -ms-keyboard-focus.
// It monitors the last input (keyboard/mouse) and adds/removes a win-keyboard class
// so that you can style .foo.win-keyboard:focus vs .foo:focus to add a keyboard rect
// on an item only when the last input method was keyboard.
// Reminder: Touch edgy does not count as an input method.
element.addEventListener("pointerdown", function (ev) {
// In case pointer down came on the active element.
WinJS.Utilities.removeClass(ev.srcElement, "win-keyboard");
}, true);
element.addEventListener("keydown", function (ev) {
WinJS.Utilities.addClass(ev.srcElement, "win-keyboard");
}, true);
element.addEventListener("focusin", function (ev) {
WinJS.UI._keyboardSeenLast && WinJS.Utilities.addClass(ev.srcElement, "win-keyboard");
}, true);
element.addEventListener("focusout", function (ev) {
WinJS.Utilities.removeClass(ev.srcElement, "win-keyboard");
}, true);
},
_KeyboardBehavior: WinJS.Namespace._lazy(function () {
return WinJS.Class.define(function KeyboardBehavior_ctor(element, options) {
// KeyboardBehavior allows you to easily convert a bunch of tabable elements into a single tab stop with
// navigation replaced by keyboard arrow (Up/Down/Left/Right) + Home + End + Custom keys.
//
// Example use cases:
//
// 1 Dimensional list: FixedDirection = height and FixedSize = 1;
// [1] [ 2 ] [ 3 ] [4] [ 5 ]...
//
// 2 Dimensional list: FixedDirection = height and FixedSize = 2;
// [1] [3] [5] [7] ...
// [2] [4] [6] [8]
//
// 1 Dimensional list: FixedDirection = width and FixedSize = 1;
// [ 1 ]
// - -
// | |
// | 2 |
// | |
// - -
// [ 3 ]
// [ 4 ]
// ...
//
// 2 Dimensional list: FixedDirection = width and FixedSize = 2;
// [1][2]
// [3][4]
// [5][6]
// ...
//
// Currently it is a "behavior" instead of a "control" so it can be attached to the same element as a
// winControl. The main scenario for this would be to attach it to the same element as a repeater.
//
// It also blocks "Portaling" where you go off the end of one column and wrap around to the other
// column. It also blocks "Carousel" where you go from the end of the list to the beginning.
//
// Keyboarding behavior supports nesting. It supports your tab stops having sub tab stops. If you want
// an interactive element within the tab stop you need to use the win-interactive classname or during the
// keydown event stop propogation so that the event is skipped.
//
// If you have custom keyboarding the getAdjacent API is provided. This can be used to enable keyboarding
// in multisize 2d lists or custom keyboard commands. PageDown and PageUp are the most common since this
// behavior does not detect scrollers.
//
// It also allows developers to show/hide keyboard focus rectangles themselves.
//
// It has an API called currentIndex so that Tab (or Shift+Tab) or a developer imitating Tab will result in
// the correct item having focus.
//
// It also allows an element to be represented as 2 arrow stops (commonly used for a split button) by calling
// the _getFocusInto API on the child element's winControl if it exists.
element = element || document.createElement("DIV");
options = options || {};
element._keyboardBehavior = this;
this._element = element;
this._fixedDirection = WinJS.UI._KeyboardBehavior.FixedDirection.width;
this._fixedSize = 1;
this._currentIndex = 0;
WinJS.UI.setOptions(this, options);
this._element.addEventListener('keydown', this._keyDownHandler.bind(this));
this._element.addEventListener('pointerdown', this._MSPointerDownHandler.bind(this));
this._element.addEventListener("beforeactivate", this._beforeActivateHandler.bind(this));
}, {
element: {
get: function () {
return this._element;
}
},
fixedDirection: {
get: function () {
return this._fixedDirection;
},
set: function (value) {
this._fixedDirection = value;
}
},
fixedSize: {
get: function () {
return this._fixedSize;
},
set: function (value) {
if (+value === value) {
value = Math.max(1, value);
this._fixedSize = value;
}
}
},
currentIndex: {
get: function () {
if (this._element.children.length > 0) {
return this._currentIndex;
}
return -1;
},
set: function (value) {
if (+value === value) {
var length = this._element.children.length;
value = Math.max(0, Math.min(length - 1, value));
this._currentIndex = value;
}
}
},
getAdjacent: {
get: function () {
return this._getAdjacent
},
set: function (value) {
this._getAdjacent = value;
}
},
_keyDownHandler: function _KeyboardBehavior_keyDownHandler(ev) {
if (!ev.altKey) {
if (ev.srcElement.msMatchesSelector(".win-interactive, .win-interactive *")) {
return;
}
var blockScrolling = false;
var newIndex = this.currentIndex;
var maxIndex = this._element.children.length - 1;
var minIndex = 0;
var rtl = getComputedStyle(this._element).direction === "rtl";
var leftStr = rtl ? "Right" : "Left";
var rightStr = rtl ? "Left" : "Right";
var targetIndex = this.getAdjacent && this.getAdjacent(newIndex, ev.key);
if (+targetIndex === targetIndex) {
blockScrolling = true;
newIndex = targetIndex;
} else {
var modFixedSize = newIndex % this.fixedSize;
if (ev.key === leftStr) {
blockScrolling = true;
if (this.fixedDirection === WinJS.UI._KeyboardBehavior.FixedDirection.width) {
if (modFixedSize !== 0) {
newIndex--;
}
} else {
if (newIndex >= this.fixedSize) {
newIndex -= this.fixedSize;
}
}
} else if (ev.key === rightStr) {
blockScrolling = true;
if (this.fixedDirection === WinJS.UI._KeyboardBehavior.FixedDirection.width) {
if (modFixedSize !== this.fixedSize - 1) {
newIndex++;
}
} else {
if (newIndex + this.fixedSize - modFixedSize <= maxIndex) {
newIndex += this.fixedSize;
}
}
} else if (ev.key === "Up") {
blockScrolling = true;
if (this.fixedDirection === WinJS.UI._KeyboardBehavior.FixedDirection.height) {
if (modFixedSize !== 0) {
newIndex--;
}
} else {
if (newIndex >= this.fixedSize) {
newIndex -= this.fixedSize;
}
}
} else if (ev.key === "Down") {
blockScrolling = true;
if (this.fixedDirection === WinJS.UI._KeyboardBehavior.FixedDirection.height) {
if (modFixedSize !== this.fixedSize - 1) {
newIndex++;
}
} else {
if (newIndex + this.fixedSize - modFixedSize <= maxIndex) {
newIndex += this.fixedSize;
}
}
} else if (ev.key === "Home") {
blockScrolling = true;
newIndex = 0;
} else if (ev.key === "End") {
blockScrolling = true;
newIndex = this._element.children.length - 1;
} else if (ev.key === "PageUp") {
blockScrolling = true;
} else if (ev.key === "PageDown") {
blockScrolling = true;
}
}
newIndex = Math.max(0, Math.min(this._element.children.length - 1, newIndex));
if (newIndex !== this.currentIndex) {
this._focus(newIndex, ev.key);
// Allow KeyboardBehavior to be nested
if (ev.key === leftStr || ev.key === rightStr || ev.key === "Up" || ev.key === "Down") {
ev.stopPropagation();
}
}
if (blockScrolling) {
ev.preventDefault();
}
}
},
_focus: function _KeyboardBehavior_focus(index, key) {
index = (+index === index) ? index : this.currentIndex;
var elementToFocus = this._element.children[index];
if (elementToFocus) {
if (elementToFocus.winControl && elementToFocus.winControl._getFocusInto) {
elementToFocus = elementToFocus.winControl._getFocusInto(key);
}
this.currentIndex = index;
try {
elementToFocus.setActive();
} catch (e) {
}
}
},
_MSPointerDownHandler: function _KeyboardBehavior_MSPointerDownHandler(ev) {
var srcElement = ev.srcElement;
if (srcElement === this.element) {
return;
}
while (srcElement.parentNode !== this.element) {
srcElement = srcElement.parentNode;
}
var index = -1;
while (srcElement) {
index++;
srcElement = srcElement.previousElementSibling;
}
this.currentIndex = index;
},
_beforeActivateHandler: function _KeyboardBehavior_beforeActivateHandler(ev) {
var allowActivate = false;
if (this._element.children.length) {
var currentItem = this._element.children[this.currentIndex];
if (currentItem === ev.srcElement || currentItem.contains(ev.srcElement)) {
allowActivate = true;
}
}
if (!allowActivate) {
ev.stopPropagation();
ev.preventDefault();
}
}
}, {
FixedDirection: {
height: "height",
width: "width"
}
})
})
});
})(this, WinJS);
(function NavBarInit(global, WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Displays navigation commands in a toolbar that the user can show or hide.
///
///
///
///
///
///
///
///
///
///
]]>
/// Raised just before showing the NavBar.
/// Raised immediately after an NavBar is fully shown.
/// Raised just before hiding the NavBar.
/// Raised immediately after the NavBar is fully hidden.
/// Fired when children of NavBar control have been processed from a WinJS.UI.processAll call.
/// Styles the entire NavBar.
///
///
///
NavBar: WinJS.Namespace._lazy(function () {
var childrenProcessedEventName = "childrenprocessed";
var createEvent = WinJS.Utilities._createEventProperty;
return WinJS.Class.derive(WinJS.UI.AppBar, function NavBar_ctor(element, options) {
///
///
/// Creates a new NavBar.
///
///
/// The DOM element that will host the new NavBar control.
///
///
/// An object that contains one or more property/value pairs to apply to the new control. Each property of the options object corresponds to one of the control's
/// properties or events.
///
///
/// The new NavBar control.
///
///
///
options = options || {};
// Shallow copy object so we can modify it.
options = WinJS.Utilities._shallowCopy(options);
// Default to Placement = Top and Layout = Custom
options.placement = options.placement || "top";
options.layout = "custom";
WinJS.UI.AppBar.call(this, element, options);
this._element.addEventListener("beforeshow", this._handleBeforeShow.bind(this));
WinJS.Utilities.addClass(this.element, WinJS.UI.NavBar._ClassName.navbar);
if (window.Windows && Windows.ApplicationModel && Windows.ApplicationModel.DesignMode && Windows.ApplicationModel.DesignMode.designModeEnabled) {
this._processChildren();
} else {
WinJS.Utilities.Scheduler.schedule(this._processChildren.bind(this), WinJS.Utilities.Scheduler.Priority.idle, null, "WinJS.UI.NavBar.processChildren");
}
}, {
// Block others from setting the layout property.
///
/// The layout of the NavBar contents.
///
///
layout: {
value: "custom",
writable: false
},
///
/// Raised when children of NavBar control have been processed by a WinJS.UI.processAll call.
///
///
onchildrenprocessed: createEvent(childrenProcessedEventName),
_processChildren: function NavBar_processChildren() {
// The NavBar control schedules processAll on its children at idle priority to avoid hurting startup
// performance. If the NavBar is shown before the scheduler gets to the idle job, the NavBar will
// immediately call processAll on its children. If your app needs the children to be processed before
// the scheduled job executes, you may call processChildren to force the processAll call.
if (!this._processed) {
this._processed = true;
this._writeProfilerMark("processChildren,StartTM");
var that = this;
var processed = WinJS.Promise.as();
if (this._processors) {
this._processors.forEach(function (processAll) {
for (var i = 0, len = that.element.children.length; i < len; i++) {
(function (child) {
processed = processed.then(function () {
processAll(child);
});
}(that.element.children[i]));
}
});
}
return processed.then(
function () {
that._writeProfilerMark("processChildren,StopTM");
that._fireEvent(WinJS.UI.NavBar._EventName.childrenProcessed);
},
function () {
that._writeProfilerMark("processChildren,StopTM");
that._fireEvent(WinJS.UI.NavBar._EventName.childrenProcessed);
}
);
}
return WinJS.Promise.wrap();
},
_show: function NavBar_show() {
// Override _show to call processChildren first.
//
if (this.disabled) {
return;
}
var that = this;
this._processChildren().then(function () {
WinJS.UI.AppBar.prototype._show.call(that);
});
},
_handleBeforeShow: function NavBar_handleBeforeShow() {
// Navbar needs to ensure its elements to have their correct height and width after AppBar changes display="none"
// to display="" and AppBar needs the elements to have their final height before it measures its own element height
// to do the slide in animation over the correct amount of pixels.
if (this._disposed) {
return;
}
var navbarcontainerEls = this.element.querySelectorAll('.win-navbarcontainer');
for (var i = 0; i < navbarcontainerEls.length; i++) {
navbarcontainerEls[i].winControl.forceLayout();
}
},
_fireEvent: function NavBar_fireEvent(type, detail) {
var event = document.createEvent("CustomEvent");
event.initCustomEvent(type, true, false, detail || {});
this.element.dispatchEvent(event);
},
_writeProfilerMark: function NavBar_writeProfilerMark(text) {
msWriteProfilerMark("WinJS.UI.NavBar:" + this._id + ":" + text);
}
}, {
_ClassName: {
navbar: "win-navbar"
},
_EventName: {
childrenProcessed: childrenProcessedEventName
},
isDeclarativeControlContainer: WinJS.Utilities.markSupportedForProcessing(function (navbar, callback) {
if (navbar._processed) {
for (var i = 0, len = navbar.element.children.length; i < len; i++) {
callback(navbar.element.children[i]);
}
} else {
navbar._processors = navbar._processors || [];
navbar._processors.push(callback);
}
})
});
})
});
})(this, WinJS);
(function NavBarContainerInit(global, WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Contains a group of NavBarCommand objects in a NavBar.
///
///
///
///
///
///
///
/// ]]>
/// Raised when a NavBarCommand is invoked.
/// Raised when the split button on a NavBarCommand is toggled.
/// Styles the entire NavBarContainer control.
///
/// Styles the page indication for the NavBarContainer.
///
/// Styles the page indication for each page.
///
/// Styles the indication of the current page.
///
/// Styles the area that contains items for the NavBarContainer.
/// Styles left and right navigation arrows.
/// Styles the left navigation arrow.
/// Styles the right navigation arrow.
///
///
///
NavBarContainer: WinJS.Namespace._lazy(function () {
var buttonFadeDelay = 3000;
var PT_TOUCH = MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch";
var MS_MANIPULATION_STATE_STOPPED = 0;
var createEvent = WinJS.Utilities._createEventProperty;
var eventNames = {
invoked: "invoked",
splittoggle: "splittoggle"
};
var strings = {
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; },
get navBarContainerViewportAriaLabel() { return WinJS.Resources._getWinJSString("ui/navBarContainerViewportAriaLabel").value; }
};
var NavBarContainer = WinJS.Class.define(function NavBarContainer_ctor(element, options) {
///
///
/// Creates a new NavBarContainer.
///
///
/// The DOM element that will host the NavBarContainer control.
///
///
/// An object that contains one or more property/value pairs to apply to the new control.
/// Each property of the options object corresponds to one of the control's properties or events.
/// Event names must begin with "on".
///
///
/// The new NavBarContainer.
///
///
///
element = element || document.createElement("DIV");
this._id = element.id || element.uniqueID;
this._writeProfilerMark("constructor,StartTM");
options = options || {};
if (element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.NavBarContainer.DuplicateConstruction", strings.duplicateConstruction);
}
// Attaching JS control to DOM element
element.winControl = this;
this._element = element;
WinJS.Utilities.addClass(this.element, WinJS.UI.NavBarContainer._ClassName.navbarcontainer);
WinJS.Utilities.addClass(this.element, "win-disposable");
this._currentManipulationState = MS_MANIPULATION_STATE_STOPPED;
this._fixedSize = false;
this._maxRows = 1;
this._sizes = {};
this._setupTree();
this._duringConstructor = true;
this._dataChangingBound = this._dataChanging.bind(this);
this._dataChangedBound = this._dataChanged.bind(this);
this._navigatedBound = this._navigated.bind(this);
WinJS.Navigation.addEventListener('navigated', this._navigatedBound);
// Don't use set options for the properties so we can control the ordering to avoid rendering multiple times.
this.layout = options.layout || WinJS.UI.Orientation.horizontal;
if (options.maxRows) {
this.maxRows = options.maxRows;
}
if (options.template) {
this.template = options.template;
}
if (options.data) {
this.data = options.data;
}
if (options.fixedSize) {
this.fixedSize = options.fixedSize;
}
// Events only
WinJS.UI._setOptions(this, options, true);
this._duringConstructor = false;
if (options.currentIndex) {
this.currentIndex = options.currentIndex;
}
this._updatePageUI();
this._writeProfilerMark("constructor,StopTM");
}, {
///
/// Gets the DOM element that hosts the NavBarContainer.
///
///
element: {
get: function () {
return this._element;
}
},
///
/// Gets or sets a Template or custom rendering function that defines the HTML of each item within the NavBarContainer.
///
///
template: {
get: function () {
return this._template;
},
set: function (value) {
this._template = value;
if (this._repeater) {
var hadFocus = this.element.contains(document.activeElement);
if (!this._duringConstructor) {
this._closeSplitIfOpen();
}
// the repeater's template is wired up to this._render() so just resetting it will rebuild the tree.
this._repeater.template = this._repeater.template;
if (!this._duringConstructor) {
this._measured = false;
this._sizes.itemMeasured = false;
this._reset();
if (hadFocus) {
this._keyboardBehavior._focus(0);
}
}
}
}
},
_render: function NavBarContainer_render(item, wrapperElement) {
var navbarCommandEl = document.createElement('div');
var template = this._template;
if (template) {
if (template.render) {
template.render(item, navbarCommandEl);
} else if (template.winControl && template.winControl.render) {
template.winControl.render(item, navbarCommandEl);
} else {
navbarCommandEl.appendChild(template(item));
}
}
// Create the NavBarCommand after calling render so that the reparenting in navbarCommand works.
var navbarCommand = new WinJS.UI.NavBarCommand(navbarCommandEl, item);
return navbarCommandEl;
},
///
/// Gets or sets the WinJS.Binding.List that provides the NavBarContainer with items to display.
///
///
data: {
get: function () {
return this._repeater && this._repeater.data;
},
set: function (value) {
if (!value) {
value = new WinJS.Binding.List();
}
if (!this._duringConstructor) {
this._closeSplitIfOpen();
}
this._removeDataChangingEvents();
this._removeDataChangedEvents();
var hadFocus = this.element.contains(document.activeElement);
if (!this._repeater) {
this._surfaceEl.innerHTML = "";
this._repeater = new WinJS.UI.Repeater(this._surfaceEl, {
template: this._render.bind(this)
});
}
this._addDataChangingEvents(value);
this._repeater.data = value;
this._addDataChangedEvents(value);
if (!this._duringConstructor) {
this._measured = false;
this._sizes.itemMeasured = false;
this._reset();
if (hadFocus) {
this._keyboardBehavior._focus(0);
}
}
}
},
///
/// Gets or sets the number of rows allowed to be used before items are placed on additional pages.
///
///
maxRows: {
get: function () {
return this._maxRows;
},
set: function (value) {
value = (+value === value) ? value : 1;
this._maxRows = Math.max(1, value);
if (!this._duringConstructor) {
this._closeSplitIfOpen();
this._measured = false;
this._reset();
}
}
},
///
/// Gets or sets a value that specifies whether the NavBarContainer has a horizontal or vertical layout. The default is "horizontal".
///
///
layout: {
get: function () {
return this._layout;
},
set: function (value) {
if (value === WinJS.UI.Orientation.vertical) {
this._layout = WinJS.UI.Orientation.vertical;
WinJS.Utilities.removeClass(this.element, WinJS.UI.NavBarContainer._ClassName.horizontal);
WinJS.Utilities.addClass(this.element, WinJS.UI.NavBarContainer._ClassName.vertical);
} else {
this._layout = WinJS.UI.Orientation.horizontal;
WinJS.Utilities.removeClass(this.element, WinJS.UI.NavBarContainer._ClassName.vertical);
WinJS.Utilities.addClass(this.element, WinJS.UI.NavBarContainer._ClassName.horizontal);
}
this._viewportEl.style.msScrollSnapType = "";
this._zooming = false;
if (!this._duringConstructor) {
this._measured = false;
this._sizes.itemMeasured = false;
this._ensureVisible(this._keyboardBehavior.currentIndex, true);
this._updatePageUI();
this._closeSplitIfOpen();
}
}
},
///
/// Gets or sets the index of the current NavBarCommand.
///
///
currentIndex: {
get: function () {
return this._keyboardBehavior.currentIndex;
},
set: function (value) {
if (value === +value) {
var hadFocus = this.element.contains(document.activeElement);
this._keyboardBehavior.currentIndex = value;
if (this._surfaceEl.children.length > 0) {
this._surfaceEl.children[this._keyboardBehavior.currentIndex].winControl._splitButtonActive = false;
}
this._ensureVisible(this._keyboardBehavior.currentIndex, true);
if (hadFocus) {
this._keyboardBehavior._focus();
}
}
}
},
///
/// Gets or sets a value that specifies whether child NavBarCommand objects should be a fixed width when there are multiple pages. A value of true indicates
/// that the NavBarCommand objects use a fixed width; a value of false indicates that they use a dynamic width.
///
///
fixedSize: {
get: function () {
return this._fixedSize;
},
set: function (value) {
this._fixedSize = !!value;
if (!this._duringConstructor) {
this._closeSplitIfOpen();
if (!this._measured) {
this._measure();
} else if (this._surfaceEl.children.length > 0) {
this._updateGridStyles();
}
}
}
},
///
/// Raised when a NavBarCommand has been invoked.
///
///
oninvoked: createEvent(eventNames.invoked),
///
/// Raised when the split button on a NavBarCommand is toggled.
///
///
onsplittoggle: createEvent(eventNames.splittoggle),
forceLayout: function NavBarContainer_forceLayout() {
///
///
/// Forces the NavBarContainer to update scroll positions and if there are internal pending measurements, it will also re-measure.
/// Use this function when making the NavBarContainer visible again after you set its style.display property to "none".
///
///
///
if (this._measured) {
if (this.layout === WinJS.UI.Orientation.horizontal) {
this._scrollPosition = this._viewportEl.scrollLeft;
} else {
this._scrollPosition = this._viewportEl.scrollTop;
}
}
this._duringForceLayout = true;
this._ensureVisible(this._keyboardBehavior.currentIndex, true);
this._updatePageUI();
this._duringForceLayout = false;
},
_navigated: function NavBarContainer_navigated() {
this._closeSplitIfOpen();
this._reset();
},
_dataChanging: function NavBarContainer_dataChanging(ev) {
// Store the element that was active so that we can detect
// if the focus went away because of the data change.
this._elementHadFocus = document.activeElement;
if (this._currentSplitNavItem && this._currentSplitNavItem.splitOpened) {
if (ev.type === "itemremoved") {
if (this._surfaceEl.children[ev.detail.index].winControl === this._currentSplitNavItem) {
this._closeSplitIfOpen();
}
} else if (ev.type === "itemchanged") {
if (this._surfaceEl.children[ev.detail.index].winControl === this._currentSplitNavItem) {
this._closeSplitIfOpen();
}
} else if (ev.type === "itemmoved") {
if (this._surfaceEl.children[ev.detail.oldIndex].winControl === this._currentSplitNavItem) {
this._closeSplitIfOpen();
}
} else if (ev.type === "reload") {
this._closeSplitIfOpen();
}
}
},
_dataChanged: function NavBarContainer_dataChanged(ev) {
this._measured = false;
if (ev.type === "itemremoved") {
if (ev.detail.index < this._keyboardBehavior.currentIndex) {
this._keyboardBehavior.currentIndex--;
} else if (ev.detail.index === this._keyboardBehavior.currentIndex) {
// This clamps if the item being removed was the last item in the list
this._keyboardBehavior.currentIndex = this._keyboardBehavior.currentIndex;
if (document.activeElement === null && this._elementHadFocus) {
this._keyboardBehavior._focus();
}
}
} else if (ev.type === "itemchanged") {
if (ev.detail.index === this._keyboardBehavior.currentIndex) {
if (document.activeElement === null && this._elementHadFocus) {
this._keyboardBehavior._focus();
}
}
} else if (ev.type === "iteminserted") {
if (ev.detail.index <= this._keyboardBehavior.currentIndex) {
this._keyboardBehavior.currentIndex++;
}
} else if (ev.type === "itemmoved") {
if (ev.detail.oldIndex === this._keyboardBehavior.currentIndex) {
this._keyboardBehavior.currentIndex = ev.detail.newIndex;
if (document.activeElement === null && this._elementHadFocus) {
this._keyboardBehavior._focus();
}
}
} else if (ev.type === "reload") {
this._keyboardBehavior.currentIndex = 0;
if (document.activeElement === null && this._elementHadFocus) {
this._keyboardBehavior._focus();
}
}
this._ensureVisible(this._keyboardBehavior.currentIndex, true);
this._updatePageUI();
},
_reset: function NavBarContainer_reset() {
this._keyboardBehavior.currentIndex = 0;
if (this._surfaceEl.children.length > 0) {
this._surfaceEl.children[this._keyboardBehavior.currentIndex].winControl._splitButtonActive = false;
}
if (this.element.contains(document.activeElement)) {
this._keyboardBehavior._focus(0);
}
this._viewportEl.style.msScrollSnapType = "";
this._zooming = false;
this._ensureVisible(0, true);
this._updatePageUI();
},
_removeDataChangedEvents: function NavBarContainer_removeDataChangedEvents() {
if (this._repeater) {
this._repeater.data.removeEventListener("itemchanged", this._dataChangedBound);
this._repeater.data.removeEventListener("iteminserted", this._dataChangedBound);
this._repeater.data.removeEventListener("itemmoved", this._dataChangedBound);
this._repeater.data.removeEventListener("itemremoved", this._dataChangedBound);
this._repeater.data.removeEventListener("reload", this._dataChangedBound);
}
},
_addDataChangedEvents: function NavBarContainer_addDataChangedEvents(bindingList) {
if (this._repeater) {
this._repeater.data.addEventListener("itemchanged", this._dataChangedBound);
this._repeater.data.addEventListener("iteminserted", this._dataChangedBound);
this._repeater.data.addEventListener("itemmoved", this._dataChangedBound);
this._repeater.data.addEventListener("itemremoved", this._dataChangedBound);
this._repeater.data.addEventListener("reload", this._dataChangedBound);
}
},
_removeDataChangingEvents: function NavBarContainer_removeDataChangingEvents() {
if (this._repeater) {
this._repeater.data.removeEventListener("itemchanged", this._dataChangingBound);
this._repeater.data.removeEventListener("iteminserted", this._dataChangingBound);
this._repeater.data.removeEventListener("itemmoved", this._dataChangingBound);
this._repeater.data.removeEventListener("itemremoved", this._dataChangingBound);
this._repeater.data.removeEventListener("reload", this._dataChangingBound);
}
},
_addDataChangingEvents: function NavBarContainer_addDataChangingEvents(bindingList) {
bindingList.addEventListener("itemchanged", this._dataChangingBound);
bindingList.addEventListener("iteminserted", this._dataChangingBound);
bindingList.addEventListener("itemmoved", this._dataChangingBound);
bindingList.addEventListener("itemremoved", this._dataChangingBound);
bindingList.addEventListener("reload", this._dataChangingBound);
},
_mouseleave: function NavBarContainer_mouseleave(ev) {
if (this._mouseInViewport) {
this._mouseInViewport = false;
this._updateArrows();
}
},
_MSPointerDown: function NavBarContainer_MSPointerDown(ev) {
if (ev.pointerType === PT_TOUCH) {
if (this._mouseInViewport) {
this._mouseInViewport = false;
this._updateArrows();
}
}
},
_MSPointerMove: function NavBarContainer_MSPointerMove(ev) {
if (ev.pointerType !== PT_TOUCH) {
if (!this._mouseInViewport) {
this._mouseInViewport = true;
this._updateArrows();
}
}
},
_setupTree: function NavBarContainer_setupTree() {
this._animateNextPreviousButtons = WinJS.Promise.wrap();
this._element.addEventListener('mouseleave', this._mouseleave.bind(this));
this._element.addEventListener('pointerdown', this._MSPointerDown.bind(this));
this._element.addEventListener('pointermove', this._MSPointerMove.bind(this));
this._element.addEventListener("focus", this._focusHandler.bind(this), true);
this._pageindicatorsEl = document.createElement('div');
WinJS.Utilities.addClass(this._pageindicatorsEl, WinJS.UI.NavBarContainer._ClassName.pageindicators);
this._element.appendChild(this._pageindicatorsEl);
this._ariaStartMarker = document.createElement("div");
this._element.appendChild(this._ariaStartMarker);
this._viewportEl = document.createElement('div');
WinJS.Utilities.addClass(this._viewportEl, WinJS.UI.NavBarContainer._ClassName.viewport);
this._element.appendChild(this._viewportEl);
this._viewportEl.setAttribute("role", "group");
this._viewportEl.setAttribute("aria-label", strings.navBarContainerViewportAriaLabel);
this._boundResizeHandler = this._resizeHandler.bind(this);
window.addEventListener("resize", this._boundResizeHandler);
this._viewportEl.addEventListener("mselementresize", this._resizeHandler.bind(this));
this._viewportEl.addEventListener("scroll", this._scrollHandler.bind(this));
this._viewportEl.addEventListener("MSManipulationStateChanged", this._MSManipulationStateChangedHandler.bind(this));
this._ariaEndMarker = document.createElement("div");
this._element.appendChild(this._ariaEndMarker);
this._surfaceEl = document.createElement('div');
WinJS.Utilities.addClass(this._surfaceEl, WinJS.UI.NavBarContainer._ClassName.surface);
this._viewportEl.appendChild(this._surfaceEl);
this._surfaceEl.addEventListener("_invoked", this._navbarCommandInvokedHandler.bind(this));
this._surfaceEl.addEventListener("_splittoggle", this._navbarCommandSplitToggleHandler.bind(this));
this._surfaceEl.addEventListener("focus", this._itemsFocusHandler.bind(this), true);
this._surfaceEl.addEventListener("keydown", this._keyDownHandler.bind(this));
// Reparent NavBarCommands which were in declarative markup
var tempEl = this.element.firstElementChild;
while (tempEl !== this._pageindicatorsEl) {
this._surfaceEl.appendChild(tempEl);
WinJS.UI.process(tempEl);
tempEl = this.element.firstElementChild;
}
this._leftArrowEl = document.createElement('div');
WinJS.Utilities.addClass(this._leftArrowEl, WinJS.UI.NavBarContainer._ClassName.navleftarrow);
WinJS.Utilities.addClass(this._leftArrowEl, WinJS.UI.NavBarContainer._ClassName.navarrow);
this._element.appendChild(this._leftArrowEl);
this._leftArrowEl.addEventListener('click', this._goLeft.bind(this));
this._leftArrowEl.style.opacity = 0;
this._leftArrowEl.style.visibility = 'hidden';
this._leftArrowFadeOut = WinJS.Promise.wrap();
this._rightArrowEl = document.createElement('div');
WinJS.Utilities.addClass(this._rightArrowEl, WinJS.UI.NavBarContainer._ClassName.navrightarrow);
WinJS.Utilities.addClass(this._rightArrowEl, WinJS.UI.NavBarContainer._ClassName.navarrow);
this._element.appendChild(this._rightArrowEl);
this._rightArrowEl.addEventListener('click', this._goRight.bind(this));
this._rightArrowEl.style.opacity = 0;
this._rightArrowEl.style.visibility = 'hidden';
this._rightArrowFadeOut = WinJS.Promise.wrap();
this._keyboardBehavior = new WinJS.UI._KeyboardBehavior(this._surfaceEl);
this._winKeyboard = new WinJS.UI._WinKeyboard(this._surfaceEl);
},
_goRight: function NavBarContainer_goRight() {
if (this._sizes.rtl) {
this._goPrev();
} else {
this._goNext();
}
},
_goLeft: function NavBarContainer_goLeft() {
if (this._sizes.rtl) {
this._goNext();
} else {
this._goPrev();
}
},
_goNext: function NavBarContainer_goNext() {
this._measure();
var itemsPerPage = this._sizes.rowsPerPage * this._sizes.columnsPerPage;
var targetPage = Math.min(Math.floor(this._keyboardBehavior.currentIndex / itemsPerPage) + 1, this._sizes.pages - 1);
this._keyboardBehavior.currentIndex = Math.min(itemsPerPage * targetPage, this._surfaceEl.children.length);
this._keyboardBehavior._focus();
},
_goPrev: function NavBarContainer_goPrev() {
this._measure();
var itemsPerPage = this._sizes.rowsPerPage * this._sizes.columnsPerPage;
var targetPage = Math.max(0, Math.floor(this._keyboardBehavior.currentIndex / itemsPerPage) - 1);
this._keyboardBehavior.currentIndex = Math.max(itemsPerPage * targetPage, 0);
this._keyboardBehavior._focus();
},
_currentPage: {
get: function () {
if (this.layout === WinJS.UI.Orientation.horizontal) {
this._measure();
if (this._sizes.viewportOffsetWidth > 0) {
return Math.min(this._sizes.pages - 1, Math.round(this._scrollPosition / this._sizes.viewportOffsetWidth));
}
}
return 0;
}
},
_resizeHandler: function NavBarContainer_resizeHandler() {
if (this._disposed) {
return;
}
if (this._measured) {
this._measured = false;
if (!this._pendingResize) {
this._pendingResize = true;
this._resizeImplBound = this._resizeImplBound || this._resizeImpl.bind(this);
if (!this._appBarEl || !this._appBarEl.contains(this.element)) {
if (this._appBarEl) {
this._appBarEl.removeEventListener('beforeshow', this._resizeImplBound);
}
var appBarEl = this.element.parentNode;
while (appBarEl && !WinJS.Utilities.hasClass(appBarEl, 'win-appbar')) {
appBarEl = appBarEl.parentNode;
}
this._appBarEl = appBarEl;
}
if (this._appBarEl && this._appBarEl.winControl && this._appBarEl.winControl.hidden) {
// Do resize lazily.
WinJS.Utilities.Scheduler.schedule(this._resizeImplBound, WinJS.Utilities.Scheduler.Priority.idle, null, "WinJS.UI.NavBarContainer._resizeImpl");
this._appBarEl.addEventListener('beforeshow', this._resizeImplBound);
} else {
// Do resize now
this._resizeImpl();
}
}
}
},
_resizeImpl: function NavBarContainer_resizeImpl() {
if (!this._disposed && this._pendingResize) {
this._pendingResize = false;
if (this._appBarEl) {
this._appBarEl.removeEventListener('beforeshow', this._resizeImplBound);
}
this._keyboardBehavior.currentIndex = 0;
if (this._surfaceEl.children.length > 0) {
this._surfaceEl.children[0].winControl._splitButtonActive = false;
}
if (this.element.contains(document.activeElement)) {
this._keyboardBehavior._focus(this._keyboardBehavior.currentIndex);
}
this._closeSplitIfOpen();
this._ensureVisible(this._keyboardBehavior.currentIndex, true);
this._updatePageUI();
}
},
_keyDownHandler: function NavBarContainer_keyDownHandler(ev) {
var key = ev.key
if (!ev.altKey && (key === "PageUp" || key === "PageDown")) {
var srcElement = ev.srcElement;
if (srcElement.msMatchesSelector(".win-interactive, .win-interactive *")) {
return;
}
var index = this._keyboardBehavior.currentIndex;
this._measure();
var sizes = this._sizes;
var page = Math.floor(index / (sizes.columnsPerPage * sizes.rowsPerPage));
var scrollPositionTarget = null;
if (key === "PageUp") {
if (this.layout === WinJS.UI.Orientation.horizontal) {
var indexOfFirstItemOnPage = page * sizes.columnsPerPage * sizes.rowsPerPage;
if (index === indexOfFirstItemOnPage && this._surfaceEl.children[index].winControl._buttonEl === document.activeElement) {
// First item on page so go back 1 page.
index = index - sizes.columnsPerPage * sizes.rowsPerPage;
} else {
// Not first item on page so go to the first item on page.
index = indexOfFirstItemOnPage;
}
} else {
var currentItem = this._surfaceEl.children[index];
var top = currentItem.offsetTop;
var bottom = top + currentItem.offsetHeight;
var scrollPosition = this._zooming ? this._zoomPosition : this._scrollPosition;
if (top >= scrollPosition && bottom < scrollPosition + sizes.viewportOffsetHeight) {
// current item is fully on screen.
while (index > 0 &&
this._surfaceEl.children[index - 1].offsetTop > scrollPosition) {
index--;
}
}
if (this._keyboardBehavior.currentIndex === index) {
var scrollPositionForOnePageAboveItem = bottom - sizes.viewportOffsetHeight;
index = Math.max(0, index - 1);
while (index > 0 &&
this._surfaceEl.children[index - 1].offsetTop > scrollPositionForOnePageAboveItem) {
index--;
}
if (index > 0) {
scrollPositionTarget = this._surfaceEl.children[index].offsetTop - this._sizes.itemMarginTop;
} else {
scrollPositionTarget = 0;
}
}
}
index = Math.max(index, 0);
this._keyboardBehavior.currentIndex = index;
var element = this._surfaceEl.children[index].winControl._buttonEl;
this._surfaceEl.children[index].winControl._splitButtonActive = false;
if (scrollPositionTarget !== null) {
this._scrollTo(scrollPositionTarget);
}
try {
element.setActive();
} catch (e) {
}
} else {
if (this.layout === WinJS.UI.Orientation.horizontal) {
var indexOfLastItemOnPage = (page + 1) * sizes.columnsPerPage * sizes.rowsPerPage - 1;
if (index === indexOfLastItemOnPage) {
// Last item on page so go forward 1 page.
index = index + sizes.columnsPerPage * sizes.rowsPerPage;
} else {
// Not Last item on page so go to last item on page.
index = indexOfLastItemOnPage;
}
} else {
var currentItem = this._surfaceEl.children[this._keyboardBehavior.currentIndex];
var top = currentItem.offsetTop;
var bottom = top + currentItem.offsetHeight;
var scrollPosition = this._zooming ? this._zoomPosition : this._scrollPosition;
if (top >= scrollPosition && bottom < scrollPosition + sizes.viewportOffsetHeight) {
// current item is fully on screen.
while (index < this._surfaceEl.children.length - 1 &&
this._surfaceEl.children[index + 1].offsetTop + this._surfaceEl.children[index + 1].offsetHeight < scrollPosition + sizes.viewportOffsetHeight) {
index++;
}
}
if (index === this._keyboardBehavior.currentIndex) {
var scrollPositionForOnePageBelowItem = top + sizes.viewportOffsetHeight;
index = Math.min(this._surfaceEl.children.length - 1, index + 1);
while (index < this._surfaceEl.children.length - 1 &&
this._surfaceEl.children[index + 1].offsetTop + this._surfaceEl.children[index + 1].offsetHeight < scrollPositionForOnePageBelowItem) {
index++;
}
if (index < this._surfaceEl.children.length - 1) {
scrollPositionTarget = this._surfaceEl.children[index + 1].offsetTop - this._sizes.viewportOffsetHeight;
} else {
scrollPositionTarget = this._scrollLength - this._sizes.viewportOffsetHeight;
}
}
}
index = Math.min(index, this._surfaceEl.children.length - 1);
this._keyboardBehavior.currentIndex = index;
var element = this._surfaceEl.children[index].winControl._buttonEl;
this._surfaceEl.children[index].winControl._splitButtonActive = false;
if (scrollPositionTarget !== null) {
this._scrollTo(scrollPositionTarget);
}
try {
element.setActive();
} catch (e) {
}
}
}
},
_focusHandler: function NavBarContainer_focusHandler(ev) {
var srcElement = ev.srcElement;
if (!this._surfaceEl.contains(srcElement)) {
// Forward focus from NavBarContainer, viewport or surface to the currentIndex.
this._skipEnsureVisible = true;
this._keyboardBehavior._focus(this._keyboardBehavior.currentIndex);
}
},
_itemsFocusHandler: function NavBarContainer_itemsFocusHandler(ev) {
// Find the item which is being focused and scroll it to view.
var srcElement = ev.srcElement;
if (srcElement === this._surfaceEl) {
return;
}
while (srcElement.parentNode !== this._surfaceEl) {
srcElement = srcElement.parentNode;
}
var index = -1;
while (srcElement) {
index++;
srcElement = srcElement.previousSibling;
}
if (this._skipEnsureVisible) {
this._skipEnsureVisible = false;
} else {
this._ensureVisible(index);
}
},
_ensureVisible: function NavBarContainer_ensureVisible(index, withoutAnimation) {
this._measure();
if (this.layout === WinJS.UI.Orientation.horizontal) {
var page = Math.floor(index / (this._sizes.rowsPerPage * this._sizes.columnsPerPage));
this._scrollTo(page * this._sizes.viewportOffsetWidth, withoutAnimation);
} else {
var element = this._surfaceEl.children[index];
var maxScrollPosition;
if (index > 0) {
maxScrollPosition = element.offsetTop - this._sizes.itemMarginTop;
} else {
maxScrollPosition = 0;
}
var minScrollPosition;
if (index < this._surfaceEl.children.length - 1) {
minScrollPosition = this._surfaceEl.children[index + 1].offsetTop - this._sizes.viewportOffsetHeight;
} else {
minScrollPosition = this._scrollLength - this._sizes.viewportOffsetHeight;
}
var newScrollPosition = this._zooming ? this._zoomPosition : this._scrollPosition;
newScrollPosition = Math.max(newScrollPosition, minScrollPosition);
newScrollPosition = Math.min(newScrollPosition, maxScrollPosition);
this._scrollTo(newScrollPosition, withoutAnimation);
}
},
_scrollTo: function NavBarContainer_scrollTo(targetScrollPosition, withoutAnimation) {
this._measure();
if (this.layout === WinJS.UI.Orientation.horizontal) {
targetScrollPosition = Math.max(0, Math.min(this._scrollLength - this._sizes.viewportOffsetWidth, targetScrollPosition));
} else {
targetScrollPosition = Math.max(0, Math.min(this._scrollLength - this._sizes.viewportOffsetHeight, targetScrollPosition));
}
if (withoutAnimation) {
if (Math.abs(this._scrollPosition - targetScrollPosition) > 1) {
this._zooming = false;
this._scrollPosition = targetScrollPosition;
this._updatePageUI();
if (!this._duringForceLayout) {
this._closeSplitIfOpen();
}
if (this.layout === WinJS.UI.Orientation.horizontal) {
this._viewportEl.scrollLeft = targetScrollPosition;
} else {
this._viewportEl.scrollTop = targetScrollPosition;
}
}
} else {
if ((!this._zooming && Math.abs(this._scrollPosition - targetScrollPosition) > 1) || (this._zooming && Math.abs(this._zoomPosition - targetScrollPosition) > 1)) {
this._zoomPosition = targetScrollPosition;
this._zooming = true;
if (this.layout === WinJS.UI.Orientation.horizontal) {
this._viewportEl.style.msScrollSnapType = "none";
this._viewportEl.msZoomTo({ contentX: targetScrollPosition, contentY: 0, viewportX: 0, viewportY: 0 });
} else {
this._viewportEl.msZoomTo({ contentX: 0, contentY: targetScrollPosition, viewportX: 0, viewportY: 0 });
}
this._closeSplitIfOpen();
}
}
},
_MSManipulationStateChangedHandler: function NavBarContainer_MSManipulationStateChangedHandler(e) {
this._currentManipulationState = e.currentState;
if (e.currentState === e.MS_MANIPULATION_STATE_ACTIVE) {
this._viewportEl.style.msScrollSnapType = "";
this._zooming = false;
}
clearTimeout(this._manipulationStateTimeoutId);
// The extra stop event is firing when an msZoomTo is called during another msZoomTo and
// also the first msZoomTo after a resize.
if (e.currentState === e.MS_MANIPULATION_STATE_STOPPED) {
this._manipulationStateTimeoutId = setTimeout(function () {
this._viewportEl.style.msScrollSnapType = "";
this._zooming = false;
this._updateCurrentIndexIfPageChanged();
}.bind(this), 100);
}
},
_scrollHandler: function NavBarContainer_scrollHandler() {
this._measured = false;
if (!this._checkingScroll) {
var that = this;
this._checkingScroll = requestAnimationFrame(function () {
that._checkingScroll = null;
var newScrollPosition;
if (that.layout === WinJS.UI.Orientation.horizontal) {
newScrollPosition = that._viewportEl.scrollLeft;
} else {
newScrollPosition = that._viewportEl.scrollTop;
}
if (newScrollPosition !== that._scrollPosition) {
that._scrollPosition = newScrollPosition;
that._closeSplitIfOpen();
}
that._updatePageUI();
if (!that._zooming && this._currentManipulationState === MS_MANIPULATION_STATE_STOPPED) {
that._updateCurrentIndexIfPageChanged();
}
});
}
},
_updateCurrentIndexIfPageChanged: function NavBarContainer_updateCurrentIndexIfPageChanged() {
// If you change pages via pagination arrows, mouse wheel, or panning we need to update the current
// item to be the first item on the new page.
if (this.layout === WinJS.UI.Orientation.horizontal) {
this._measure();
var currentPage = this._currentPage;
var firstIndexOnPage = currentPage * this._sizes.rowsPerPage * this._sizes.columnsPerPage;
var lastIndexOnPage = (currentPage + 1) * this._sizes.rowsPerPage * this._sizes.columnsPerPage - 1;
if (this._keyboardBehavior.currentIndex < firstIndexOnPage || this._keyboardBehavior.currentIndex > lastIndexOnPage) {
// Page change occurred.
this._keyboardBehavior.currentIndex = firstIndexOnPage;
if (this._surfaceEl.children.length > 0) {
this._surfaceEl.children[this._keyboardBehavior.currentIndex].winControl._splitButtonActive = false;
}
if (this.element.contains(document.activeElement)) {
this._keyboardBehavior._focus(this._keyboardBehavior.currentIndex);
}
}
}
},
_measure: function NavBarContainer_measure() {
if (!this._measured) {
this._resizeImpl();
this._writeProfilerMark("measure,StartTM");
var sizes = this._sizes;
sizes.rtl = getComputedStyle(this._element).direction === "rtl";
var itemCount = this._surfaceEl.children.length;
if (itemCount > 0) {
if (!this._sizes.itemMeasured) {
this._writeProfilerMark("measureItem,StartTM");
var elementToMeasure = this._surfaceEl.firstElementChild;
// Clear inline margins set by NavBarContainer before measuring.
elementToMeasure.style.margin = "";
elementToMeasure.style.width = "";
var elementComputedStyle = getComputedStyle(elementToMeasure);
sizes.itemOffsetWidth = parseFloat(getComputedStyle(elementToMeasure).width);
if (elementToMeasure.offsetWidth === 0) {
sizes.itemOffsetWidth = 0;
}
sizes.itemMarginLeft = parseFloat(elementComputedStyle.marginLeft);
sizes.itemMarginRight = parseFloat(elementComputedStyle.marginRight);
sizes.itemWidth = sizes.itemOffsetWidth + sizes.itemMarginLeft + sizes.itemMarginRight;
sizes.itemOffsetHeight = parseFloat(getComputedStyle(elementToMeasure).height);
if (elementToMeasure.offsetHeight === 0) {
sizes.itemOffsetHeight = 0;
}
sizes.itemMarginTop = parseFloat(elementComputedStyle.marginTop);
sizes.itemMarginBottom = parseFloat(elementComputedStyle.marginBottom);
sizes.itemHeight = sizes.itemOffsetHeight + sizes.itemMarginTop + sizes.itemMarginBottom;
if (sizes.itemOffsetWidth > 0 && sizes.itemOffsetHeight > 0) {
sizes.itemMeasured = true;
}
this._writeProfilerMark("measureItem,StopTM");
}
sizes.viewportOffsetWidth = parseFloat(getComputedStyle(this._viewportEl).width);
if (this._viewportEl.offsetWidth === 0) {
sizes.viewportOffsetWidth = 0;
}
sizes.viewportOffsetHeight = parseFloat(getComputedStyle(this._viewportEl).height);
if (this._viewportEl.offsetHeight === 0) {
sizes.viewportOffsetHeight = 0;
}
if (sizes.viewportOffsetWidth === 0 || sizes.itemOffsetHeight === 0) {
this._measured = false;
} else {
this._measured = true;
}
if (this.layout === WinJS.UI.Orientation.horizontal) {
this._scrollPosition = this._viewportEl.scrollLeft;
sizes.leadingEdge = this._leftArrowEl.offsetWidth + parseInt(getComputedStyle(this._leftArrowEl).marginLeft) + parseInt(getComputedStyle(this._leftArrowEl).marginRight);
var usableSpace = sizes.viewportOffsetWidth - sizes.leadingEdge * 2;
sizes.maxColumns = sizes.itemWidth ? Math.max(1, Math.floor(usableSpace / sizes.itemWidth)) : 1;
sizes.rowsPerPage = Math.min(this.maxRows, Math.ceil(itemCount / sizes.maxColumns));
sizes.columnsPerPage = Math.min(sizes.maxColumns, itemCount);
sizes.pages = Math.ceil(itemCount / (sizes.columnsPerPage * sizes.rowsPerPage));
sizes.trailingEdge = sizes.leadingEdge;
sizes.extraSpace = usableSpace - (sizes.columnsPerPage * sizes.itemWidth);
this._scrollLength = sizes.viewportOffsetWidth * sizes.pages;
this._keyboardBehavior.fixedSize = sizes.rowsPerPage;
this._keyboardBehavior.fixedDirection = WinJS.UI._KeyboardBehavior.FixedDirection.height;
this._surfaceEl.style.height = (sizes.itemHeight * sizes.rowsPerPage) + "px";
this._surfaceEl.style.width = this._scrollLength + "px";
} else {
this._scrollPosition = this._viewportEl.scrollTop;
sizes.leadingEdge = 0;
sizes.rowsPerPage = itemCount;
sizes.columnsPerPage = 1;
sizes.pages = 1;
sizes.trailingEdge = 0;
// Reminder there is margin collapsing so just use scrollHeight instead of itemHeight * itemCount
this._scrollLength = this._viewportEl.scrollHeight;
this._keyboardBehavior.fixedSize = sizes.columnsPerPage;
this._keyboardBehavior.fixedDirection = WinJS.UI._KeyboardBehavior.FixedDirection.width;
this._surfaceEl.style.height = "";
this._surfaceEl.style.width = "";
}
this._updateGridStyles();
} else {
sizes.pages = 1;
this._hasPreviousContent = false;
this._hasNextContent = false;
this._surfaceEl.style.height = "";
this._surfaceEl.style.width = "";
}
this._writeProfilerMark("measure,StopTM");
}
},
_updateGridStyles: function NavBarContainer_updateGridStyles() {
var sizes = this._sizes;
var itemCount = this._surfaceEl.children.length;
for (var index = 0; index < itemCount; index++) {
var itemEl = this._surfaceEl.children[index];
var marginRight;
var marginLeft;
var cssRow;
var cssColumn;
var width = "";
if (this.layout === WinJS.UI.Orientation.horizontal) {
var row = Math.floor(index % sizes.rowsPerPage);
var column = Math.floor(index / sizes.rowsPerPage);
var isFirstColumnOnPage = column % sizes.columnsPerPage === 0;
var isLastColumnOnPage = column % sizes.columnsPerPage === sizes.columnsPerPage - 1;
var extraTrailingMargin = sizes.trailingEdge;
if (this.fixedSize) {
extraTrailingMargin += sizes.extraSpace;
} else {
var spaceToDistribute = sizes.extraSpace - (sizes.maxColumns - sizes.columnsPerPage) * sizes.itemWidth;
width = (sizes.itemOffsetWidth + (spaceToDistribute / sizes.maxColumns)) + "px";
}
cssRow = row + 1;
cssColumn = column + 1;
var extraMarginRight;
var extraMarginLeft;
if (sizes.rtl) {
extraMarginRight = (isFirstColumnOnPage ? sizes.leadingEdge : 0);
extraMarginLeft = (isLastColumnOnPage ? extraTrailingMargin : 0);
} else {
extraMarginRight = (isLastColumnOnPage ? extraTrailingMargin : 0);
extraMarginLeft = (isFirstColumnOnPage ? sizes.leadingEdge : 0);
}
marginRight = extraMarginRight + sizes.itemMarginRight + "px";
marginLeft = extraMarginLeft + sizes.itemMarginLeft + "px";
} else {
cssRow = "";
cssColumn = "";
marginRight = "";
marginLeft = "";
}
if (itemEl.style.msGridRow !== cssRow) {
itemEl.style.msGridRow = cssRow;
}
if (itemEl.style.msGridColumn !== cssColumn) {
itemEl.style.msGridColumn = cssColumn;
}
if (itemEl.style.marginRight !== marginRight) {
itemEl.style.marginRight = marginRight;
}
if (itemEl.style.marginLeft !== marginLeft) {
itemEl.style.marginLeft = marginLeft;
}
if (itemEl.style.width !== width) {
itemEl.style.width = width;
}
}
},
_updatePageUI: function NavBarContainer_updatePageUI() {
this._measure();
var currentPage = this._currentPage;
this._hasPreviousContent = (currentPage !== 0);
this._hasNextContent = (currentPage < this._sizes.pages - 1);
this._updateArrows();
// Always output the pagination indicators so they reserves up space.
if (this._indicatorCount !== this._sizes.pages) {
this._indicatorCount = this._sizes.pages;
this._pageindicatorsEl.innerHTML = new Array(this._sizes.pages + 1).join('');
}
for (var i = 0; i < this._pageindicatorsEl.children.length; i++) {
if (i === currentPage) {
WinJS.Utilities.addClass(this._pageindicatorsEl.children[i], WinJS.UI.NavBarContainer._ClassName.currentindicator);
} else {
WinJS.Utilities.removeClass(this._pageindicatorsEl.children[i], WinJS.UI.NavBarContainer._ClassName.currentindicator);
}
}
if (this._sizes.pages > 1) {
this._viewportEl.style.overflowX = "";
this._pageindicatorsEl.style.visibility = "";
} else {
this._viewportEl.style.overflowX = "hidden";
this._pageindicatorsEl.style.visibility = "hidden";
}
if (this._sizes.pages <= 1 || this._layout !== WinJS.UI.Orientation.horizontal) {
this._ariaStartMarker.removeAttribute("aria-flowto");
this._ariaEndMarker.removeAttribute("x-ms-aria-flowfrom");
} else {
var firstIndexOnCurrentPage = currentPage * this._sizes.rowsPerPage * this._sizes.columnsPerPage;
var firstItem = this._surfaceEl.children[firstIndexOnCurrentPage].winControl._buttonEl;
WinJS.UI._ensureId(firstItem);
this._ariaStartMarker.setAttribute("aria-flowto", firstItem.id);
var lastIndexOnCurrentPage = Math.min(this._surfaceEl.children.length - 1, (currentPage + 1) * this._sizes.rowsPerPage * this._sizes.columnsPerPage - 1);
var lastItem = this._surfaceEl.children[lastIndexOnCurrentPage].winControl._buttonEl;
WinJS.UI._ensureId(lastItem);
this._ariaEndMarker.setAttribute("x-ms-aria-flowfrom", lastItem.id);
}
},
_closeSplitIfOpen: function NavBarContainer_closeSplitIfOpen() {
if (this._currentSplitNavItem) {
if (this._currentSplitNavItem.splitOpened) {
this._currentSplitNavItem._toggleSplit();
}
this._currentSplitNavItem = null;
}
},
_updateArrows: function NavBarContainer_updateArrows() {
var hasLeftContent = this._sizes.rtl ? this._hasNextContent : this._hasPreviousContent;
var hasRightContent = this._sizes.rtl ? this._hasPreviousContent : this._hasNextContent;
var that = this;
// Previous and next are the arrows, not states. On mouse hover the arrows fade in immediately. If you
// mouse out the arrows fade out after a delay. When you reach the last/first page, the corresponding
// arrow fades out immediately as well.
if (this._mouseInViewport && hasLeftContent) {
this._leftArrowWaitingToFadeOut && this._leftArrowWaitingToFadeOut.cancel();
this._leftArrowWaitingToFadeOut = null;
this._leftArrowFadeOut && this._leftArrowFadeOut.cancel();
this._leftArrowFadeOut = null;
this._leftArrowEl.style.visibility = '';
this._leftArrowFadeIn = this._leftArrowFadeIn || WinJS.UI.Animation.fadeIn(this._leftArrowEl);
} else {
if (hasLeftContent) {
// If we need a delayed fade out and we are already running a delayed fade out just use that one, don't extend it.
// Otherwise create a delayed fade out.
this._leftArrowWaitingToFadeOut = this._leftArrowWaitingToFadeOut || WinJS.Promise.timeout(buttonFadeDelay);
} else {
// If we need a immediate fade out and already have a delayed fade out cancel that one and create an immediate one.
this._leftArrowWaitingToFadeOut && this._leftArrowWaitingToFadeOut.cancel();
this._leftArrowWaitingToFadeOut = WinJS.Promise.wrap();
}
this._leftArrowWaitingToFadeOut.then(function () {
// After the delay cancel any fade in if running. If we already were fading out continue it otherwise start the fade out.
this._leftArrowFadeIn && this._leftArrowFadeIn.cancel();
this._leftArrowFadeIn = null;
this._leftArrowFadeOut = this._leftArrowFadeOut || WinJS.UI.Animation.fadeOut(this._leftArrowEl).then(function () {
that._leftArrowEl.style.visibility = 'hidden';
});
}.bind(this));
}
// Same pattern for Next arrow.
if (this._mouseInViewport && hasRightContent) {
this._rightArrowWaitingToFadeOut && this._rightArrowWaitingToFadeOut.cancel();
this._rightArrowWaitingToFadeOut = null;
this._rightArrowFadeOut && this._rightArrowFadeOut.cancel();
this._rightArrowFadeOut = null;
this._rightArrowEl.style.visibility = '';
this._rightArrowFadeIn = this._rightArrowFadeIn || WinJS.UI.Animation.fadeIn(this._rightArrowEl);
} else {
if (hasRightContent) {
this._rightArrowWaitingToFadeOut = this._rightArrowWaitingToFadeOut || WinJS.Promise.timeout(buttonFadeDelay);
} else {
this._rightArrowWaitingToFadeOut && this._rightArrowWaitingToFadeOut.cancel();
this._rightArrowWaitingToFadeOut = WinJS.Promise.wrap();
}
this._rightArrowWaitingToFadeOut.then(function () {
this._rightArrowFadeIn && this._rightArrowFadeIn.cancel();
this._rightArrowFadeIn = null;
this._rightArrowFadeOut = this._rightArrowFadeOut || WinJS.UI.Animation.fadeOut(this._rightArrowEl).then(function () {
that._rightArrowEl.style.visibility = 'hidden';
});
}.bind(this));
}
},
_navbarCommandInvokedHandler: function NavBarContainer_navbarCommandInvokedHandler(ev) {
var srcElement = ev.srcElement;
var index = -1;
while (srcElement) {
index++;
srcElement = srcElement.previousSibling;
}
this._fireEvent(WinJS.UI.NavBarContainer._EventName.invoked, {
index: index,
navbarCommand: ev.srcElement.winControl,
data: this._repeater ? this._repeater.data.getAt(index) : null
});
},
_navbarCommandSplitToggleHandler: function NavBarContainer_navbarCommandSplitToggleHandler(ev) {
var srcElement = ev.srcElement;
var index = -1;
while (srcElement) {
index++;
srcElement = srcElement.previousSibling;
}
var navbarCommand = ev.srcElement.winControl;
this._closeSplitIfOpen();
if (navbarCommand.splitOpened) {
this._currentSplitNavItem = navbarCommand;
}
this._fireEvent(WinJS.UI.NavBarContainer._EventName.splitToggle, {
opened: navbarCommand.splitOpened,
index: index,
navbarCommand: navbarCommand,
data: this._repeater ? this._repeater.data.getAt(index) : null
});
},
_fireEvent: function NavBarContainer_fireEvent(type, detail) {
var event = document.createEvent("CustomEvent");
event.initCustomEvent(type, true, false, detail);
this.element.dispatchEvent(event);
},
_writeProfilerMark: function NavBarContainer_writeProfilerMark(text) {
var message = "WinJS.UI.NavBarContainer:" + this._id + ":" + text;
msWriteProfilerMark(message);
WinJS.log && WinJS.log(message, null, "navbarcontainerprofiler");
},
dispose: function NavBarContainer_dispose() {
///
///
/// Disposes this control.
///
///
///
if (this._disposed) {
return;
}
this._disposed = true;
if (this._appBarEl) {
this._appBarEl.removeEventListener('beforeshow', this._resizeImplBound);
}
WinJS.Navigation.removeEventListener('navigated', this._navigatedBound);
this._leftArrowWaitingToFadeOut && this._leftArrowWaitingToFadeOut.cancel();
this._leftArrowFadeOut && this._leftArrowFadeOut.cancel();
this._leftArrowFadeIn && this._leftArrowFadeIn.cancel();
this._rightArrowWaitingToFadeOut && this._rightArrowWaitingToFadeOut.cancel();
this._rightArrowFadeOut && this._rightArrowFadeOut.cancel();
this._rightArrowFadeIn && this._rightArrowFadeIn.cancel();
window.removeEventListener("resize", this._boundResizeHandler);
this._removeDataChangingEvents();
this._removeDataChangedEvents();
}
}, {
// Names of classes used by the NavBarContainer.
_ClassName: {
navbarcontainer: "win-navbarcontainer",
pageindicators: "win-navbarcontainer-pageindicator-box",
indicator: "win-navbarcontainer-pageindicator",
currentindicator: "win-navbarcontainer-pageindicator-current",
vertical: "win-navbarcontainer-vertical",
horizontal: "win-navbarcontainer-horizontal",
viewport: "win-navbarcontainer-viewport",
surface: "win-navbarcontainer-surface",
navarrow: "win-navbarcontainer-navarrow",
navleftarrow: "win-navbarcontainer-navleft",
navrightarrow: "win-navbarcontainer-navright"
},
_EventName: {
invoked: eventNames.invoked,
splitToggle: eventNames.splittoggle
}
});
WinJS.Class.mix(NavBarContainer, WinJS.UI.DOMEventMixin);
return NavBarContainer;
})
});
})(this, WinJS);
(function NavBarCommandInit(global, WinJS, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
_WinPressed: WinJS.Namespace._lazy(function () {
return WinJS.Class.define(function _WinPressed_ctor(element) {
// WinPressed is the combination of :hover:active
// :hover is delayed by trident for touch by 300ms so if you want :hover:active to work quickly you need to
// use this behavior.
// :active does not bubble to its parent like :hover does so this is also useful for that scenario.
this._element = element;
this._element.addEventListener("pointerdown", this._MSPointerDownButtonHandler.bind(this));
}, {
_MSPointerDownButtonHandler: function _WinPressed_MSPointerDownButtonHandler(ev) {
if (!this._pointerUpBound) {
this._pointerUpBound = this._MSPointerUpHandler.bind(this);
this._pointerCancelBound = this._MSPointerCancelHandler.bind(this);
this._pointerOverBound = this._MSPointerOverHandler.bind(this);
this._pointerOutBound = this._MSPointerOutHandler.bind(this);
}
if (ev.isPrimary) {
if (this._pointerId) {
this._resetPointer();
}
if (!ev.srcElement.msMatchesSelector(".win-interactive, .win-interactive *")) {
this._pointerId = ev.pointerId;
window.addEventListener("pointerup", this._pointerUpBound, true);
window.addEventListener("pointercancel", this._pointerCancelBound), true;
this._element.addEventListener("pointerover", this._pointerOverBound, true);
this._element.addEventListener("pointerout", this._pointerOutBound, true);
WinJS.Utilities.addClass(this._element, WinJS.UI._WinPressed.winPressed);
}
}
},
_MSPointerOverHandler: function _WinPressed_MSPointerOverHandler(ev) {
if (this._pointerId === ev.pointerId) {
WinJS.Utilities.addClass(this._element, WinJS.UI._WinPressed.winPressed);
}
},
_MSPointerOutHandler: function _WinPressed_MSPointerOutHandler(ev) {
if (this._pointerId === ev.pointerId) {
WinJS.Utilities.removeClass(this._element, WinJS.UI._WinPressed.winPressed);
}
},
_MSPointerCancelHandler: function _WinPressed_MSPointerCancelHandler(ev) {
if (this._pointerId === ev.pointerId) {
this._resetPointer();
}
},
_MSPointerUpHandler: function _WinPressed_MSPointerUpHandler(ev) {
if (this._pointerId === ev.pointerId) {
this._resetPointer();
}
},
_resetPointer: function _WinPressed_resetPointer() {
this._pointerId = null;
window.removeEventListener("pointerup", this._pointerUpBound, true);
window.removeEventListener("pointercancel", this._pointerCancelBound, true);
this._element.removeEventListener("pointerover", this._pointerOverBound, true);
this._element.removeEventListener("pointerout", this._pointerOutBound, true);
WinJS.Utilities.removeClass(this._element, WinJS.UI._WinPressed.winPressed);
},
dispose: function _WinPressed_dispose() {
if (this._disposed) {
return;
}
this._disposed = true;
this._resetPointer();
}
}, {
winPressed: "win-pressed"
})
}),
///
///
/// Represents a navigation command in an NavBarContainer.
///
///
///
///
///
/// ]]>
/// Styles the entire NavBarCommand control.
/// Styles the main button in a NavBarCommand.
/// Styles the split button in a NavBarCommand
/// Styles the icon in the main button of a NavBarCommand.
/// Styles the label in the main button of a NavBarCommand.
///
///
///
NavBarCommand: WinJS.Namespace._lazy(function () {
var strings = {
get duplicateConstruction() { return WinJS.Resources._getWinJSString("ui/duplicateConstruction").value; }
};
var NavBarCommand = WinJS.Class.define(function NavBarCommand_ctor(element, options) {
///
///
/// Creates a new NavBarCommand.
///
///
/// The DOM element that will host the new NavBarCommand control.
///
///
/// An object that contains one or more property/value pairs to apply to the new control.
/// Each property of the options object corresponds to one of the control's properties or events.
/// Event names must begin with "on".
///
///
/// The new NavBarCommand.
///
///
///
element = element || document.createElement("DIV");
options = options || {};
if (element.winControl) {
throw new WinJS.ErrorFromName("WinJS.UI.NavBarCommand.DuplicateConstruction", strings.duplicateConstruction);
}
// Attaching JS control to DOM element
element.winControl = this;
this._element = element;
WinJS.Utilities.addClass(this.element, WinJS.UI.NavBarCommand._ClassName.navbarcommand);
WinJS.Utilities.addClass(this.element, "win-disposable");
this._tooltip = null;
this._splitOpened = false;
this._buildDom();
element.addEventListener('keydown', this._keydownHandler.bind(this));
WinJS.UI.setOptions(this, options);
}, {
///
/// Gets the DOM element that hosts the NavBarCommand.
///
///
element: {
get: function () {
return this._element;
}
},
///
/// Gets or sets the label of the NavBarCommand.
///
///
label: {
get: function () {
return this._label;
},
set: function (value) {
this._label = value;
this._labelEl.textContent = value;
}
},
///
/// Gets or sets the tooltip of the NavBarCommand.
///
///
tooltip: {
get: function () {
return this._tooltip;
},
set: function (value) {
this._tooltip = value;
if (this._tooltip || this._tooltip === "") {
this._element.setAttribute('title', this._tooltip);
} else {
this._element.removeAttribute('title');
}
}
},
///
/// Gets or sets the icon of the NavBarCommand. This value is either one of the values of the AppBarIcon enumeration or the path of a custom PNG file.
///
///
icon: {
get: function () {
return this._icon;
},
set: function (value) {
this._icon = (WinJS.UI.AppBarIcon[value] || value);
// If the icon's a single character, presume a glyph
if (this._icon && this._icon.length === 1) {
// Set the glyph
this._imageSpan.innerText = this._icon;
this._imageSpan.style.backgroundImage = "";
this._imageSpan.style.msHighContrastAdjust = "";
this._imageSpan.style.display = "";
} else if (this._icon && this._icon.length > 1) {
// Must be an image, set that
this._imageSpan.innerText = "";
this._imageSpan.style.backgroundImage = this._icon;
this._imageSpan.style.msHighContrastAdjust = "none";
this._imageSpan.style.display = "";
} else {
this._imageSpan.innerText = "";
this._imageSpan.style.backgroundImage = "";
this._imageSpan.style.msHighContrastAdjust = "";
this._imageSpan.style.display = "none";
}
}
},
///
/// Gets or sets the command's target location.
///
///
location: {
get: function () {
return this._location;
},
set: function (value) {
this._location = value;
}
},
///
/// Gets or sets the state value used for navigation. The command passes this object to the WinJS.Navigation.navigate function.
///
///
state: {
get: function () {
return this._state;
},
set: function (value) {
this._state = value;
}
},
///
/// Gets or sets a value that specifies whether the NavBarCommand has a split button.
///
///
splitButton: {
get: function () {
return this._split;
},
set: function (value) {
this._split = value;
if (this._split) {
this._splitButtonEl.style.display = "flex";
} else {
this._splitButtonEl.style.display = "none";
}
}
},
///
/// Gets or sets a value that specifies whether the split button is open.
///
///
splitOpened: {
get: function () {
return this._splitOpened;
},
set: function (value) {
if (this._splitOpened !== !!value) {
this._toggleSplit();
}
}
},
_toggleSplit: function NavBarCommand_toggleSplit() {
this._splitOpened = !this._splitOpened;
if (this._splitOpened) {
WinJS.Utilities.addClass(this._splitButtonEl, WinJS.UI.NavBarCommand._ClassName.navbarcommandsplitbuttonopened);
this._splitButtonEl.setAttribute("aria-expanded", "true");
} else {
WinJS.Utilities.removeClass(this._splitButtonEl, WinJS.UI.NavBarCommand._ClassName.navbarcommandsplitbuttonopened);
this._splitButtonEl.setAttribute("aria-expanded", "false");
}
this._fireEvent(WinJS.UI.NavBarCommand._EventName._splitToggle);
},
_rtl: {
get: function () {
return window.getComputedStyle(this.element).direction === "rtl";
}
},
_keydownHandler: function NavBarCommand_keydownHandler(ev) {
if (ev.srcElement.msMatchesSelector(".win-interactive, .win-interactive *")) {
return;
}
var leftStr = this._rtl ? "Right" : "Left";
var rightStr = this._rtl ? "Left" : "Right";
if (!ev.altKey && (ev.key === leftStr || ev.key === "Home" || ev.key === "End") && ev.srcElement === this._splitButtonEl) {
this._splitButtonActive = false;
try {
this._buttonEl.setActive();
} catch (e) {
}
if (ev.key === leftStr) {
ev.stopPropagation();
}
ev.preventDefault();
} else if (!ev.altKey && ev.key === rightStr && this.splitButton && (ev.srcElement === this._buttonEl || this._buttonEl.contains(ev.srcElement))) {
this._splitButtonActive = true;
try {
this._splitButtonEl.setActive();
} catch (e) {
}
if (ev.key === rightStr) {
ev.stopPropagation();
}
ev.preventDefault();
} else if ((ev.key === "Spacebar" || ev.key === "Enter") && (ev.srcElement === this._buttonEl || this._buttonEl.contains(ev.srcElement))) {
if (this.location) {
WinJS.Navigation.navigate(this.location, this.state);
}
this._fireEvent(WinJS.UI.NavBarCommand._EventName._invoked);
} else if ((ev.key === "Spacebar" || ev.key === "Enter") && ev.srcElement === this._splitButtonEl) {
this._toggleSplit();
}
},
_getFocusInto: function NavBarCommand_getFocusInto(key) {
var leftStr = this._rtl ? "Right" : "Left";
if ((key === leftStr) && this.splitButton) {
this._splitButtonActive = true;
return this._splitButtonEl;
} else {
this._splitButtonActive = false;
return this._buttonEl;
}
},
_buildDom: function NavBarCommand_buildDom() {
var markup =
'
' +
'
' +
'' +
'' +
'
' +
'
' +
'';
this.element.insertAdjacentHTML("afterBegin", markup);
this._buttonEl = this.element.firstElementChild;
this._buttonPressedBehavior = new WinJS.UI._WinPressed(this._buttonEl);
this._contentEl = this._buttonEl.firstElementChild;
this._imageSpan = this._contentEl.firstElementChild;
this._imageSpan.style.display = "none";
this._labelEl = this._imageSpan.nextElementSibling;
this._splitButtonEl = this._buttonEl.nextElementSibling;
this._splitButtonPressedBehavior = new WinJS.UI._WinPressed(this._splitButtonEl);
this._splitButtonEl.style.display = "none";
WinJS.UI._ensureId(this._buttonEl);
this._splitButtonEl.setAttribute("aria-labelledby", this._buttonEl.id);
this._buttonEl.addEventListener("click", this._handleButtonClick.bind(this));
this._buttonEl.addEventListener("beforeactivate", this._beforeactivateButtonHandler.bind(this));
this._buttonEl.addEventListener("pointerdown", this._MSPointerDownButtonHandler.bind(this));
var mutationObserver = new MutationObserver(this._splitButtonAriaExpandedPropertyChangeHandler.bind(this));
mutationObserver.observe(this._splitButtonEl, { attributes: true, attributeFilter: ["aria-expanded"] });
this._splitButtonEl.addEventListener("click", this._handleSplitButtonClick.bind(this));
this._splitButtonEl.addEventListener("beforeactivate", this._beforeactivateSplitButtonHandler.bind(this));
this._splitButtonEl.addEventListener("pointerdown", this._MSPointerDownSplitButtonHandler.bind(this));
// reparent any other elements.
var tempEl = this._splitButtonEl.nextSibling;
while (tempEl) {
this._buttonEl.insertBefore(tempEl, this._contentEl);
if (tempEl.nodeName !== "#text") {
WinJS.UI.processAll(tempEl);
}
tempEl = this._splitButtonEl.nextSibling;
}
},
_MSPointerDownButtonHandler: function NavBarCommand_MSPointerDownButtonHandler(ev) {
this._splitButtonActive = false;
},
_MSPointerDownSplitButtonHandler: function NavBarCommand_MSPointerDownSplitButtonHandler(ev) {
this._splitButtonActive = true;
},
_handleButtonClick: function NavBarCommand_handleButtonClick(ev) {
var srcElement = ev.srcElement;
if (!srcElement.msMatchesSelector(".win-interactive, .win-interactive *")) {
if (this.location) {
WinJS.Navigation.navigate(this.location, this.state);
}
this._fireEvent(WinJS.UI.NavBarCommand._EventName._invoked);
}
},
_splitButtonAriaExpandedPropertyChangeHandler: function NavBarCommand_splitButtonAriaExpandedPropertyChangeHandler() {
if ((this._splitButtonEl.getAttribute("aria-expanded") === "true") !== this._splitOpened) {
this._toggleSplit();
}
},
_handleSplitButtonClick: function NavBarCommand_handleSplitButtonClick(ev) {
this._toggleSplit();
},
_beforeactivateSplitButtonHandler: function NavBarCommand_beforeactivateSplitButtonHandler(ev) {
if (!this._splitButtonActive) {
ev.stopPropagation();
ev.preventDefault();
}
},
_beforeactivateButtonHandler: function NavBarCommand_beforeactivateButtonHandler(ev) {
if (this._splitButtonActive) {
ev.stopPropagation();
ev.preventDefault();
}
},
_fireEvent: function NavBarCommand_fireEvent(type, detail) {
var event = document.createEvent("CustomEvent");
event.initCustomEvent(type, true, false, detail);
this.element.dispatchEvent(event);
},
dispose: function NavBarCommand_dispose() {
///
///
/// Disposes this control.
///
///
///
if (this._disposed) {
return;
}
this._disposed = true;
this._buttonPressedBehavior.dispose();
this._splitButtonPressedBehavior.dispose();
}
}, {
_ClassName: {
navbarcommand: "win-navbarcommand",
navbarcommandbutton: "win-navbarcommand-button",
navbarcommandbuttoncontent: "win-navbarcommand-button-content",
navbarcommandsplitbutton: "win-navbarcommand-splitbutton",
navbarcommandsplitbuttonopened: "win-navbarcommand-splitbutton-opened",
navbarcommandicon: "win-navbarcommand-icon",
navbarcommandlabel: "win-navbarcommand-label"
},
_EventName: {
_invoked: "_invoked",
_splitToggle: "_splittoggle"
}
});
WinJS.Class.mix(NavBarCommand, WinJS.UI.DOMEventMixin);
return NavBarCommand;
})
});
})(this, WinJS);
(function tooltipInit(global) {
"use strict";
// Tooltip control implementation
WinJS.Namespace.define("WinJS.UI", {
///
///
/// Displays a tooltip that can contain images and formatting.
///
///
///
///
///
/// ]]>
/// Raised when the tooltip is about to appear.
/// Raised when the tooltip is showing.
/// Raised when the tooltip is about to become hidden.
/// Raised when the tooltip is hidden.
/// The entire Tooltip control.
///
///
///
Tooltip: WinJS.Namespace._lazy(function () {
var lastCloseTime = 0;
var utilities = WinJS.Utilities;
var animation = WinJS.UI.Animation;
// Constants definition
var DEFAULT_PLACEMENT = "top";
var DELAY_INITIAL_TOUCH_SHORT = 400;
var DELAY_INITIAL_TOUCH_LONG = 1200;
var DEFAULT_MOUSE_HOVER_TIME = 400; // 0.4 second
var DEFAULT_MESSAGE_DURATION = 5000; // 5 secs
var DELAY_RESHOW_NONINFOTIP_TOUCH = 0;
var DELAY_RESHOW_NONINFOTIP_NONTOUCH = 600;
var DELAY_RESHOW_INFOTIP_TOUCH = 400;
var DELAY_RESHOW_INFOTIP_NONTOUCH = 600;
var RESHOW_THRESHOLD = 200;
var HIDE_DELAY_MAX = 300000; // 5 mins
var OFFSET_KEYBOARD = 12;
var OFFSET_MOUSE = 20;
var OFFSET_TOUCH = 45;
var OFFSET_PROGRAMMATIC_TOUCH = 20;
var OFFSET_PROGRAMMATIC_NONTOUCH = 12;
var SAFETY_NET_GAP = 1; // We set a 1-pixel gap between the right or bottom edge of the tooltip and the viewport to avoid possible re-layout
var PT_TOUCH = MSPointerEvent.MSPOINTER_TYPE_TOUCH || "touch"; // pointer type to indicate a touch event
var EVENTS_INVOKE = { "keyup": "", "pointerover": "" },
EVENTS_UPDATE = { "pointermove": "" },
EVENTS_DISMISS = { "pointerdown": "", "keydown": "", "blur": "", "pointerout": "", "pointercancel": "", "pointerup": "" },
EVENTS_BY_CHILD = { "pointerover": "", "pointerout": "" };
// CSS class names
var msTooltip = "win-tooltip",
msTooltipPhantom = "win-tooltip-phantom";
// Global attributes
var mouseHoverTime = DEFAULT_MOUSE_HOVER_TIME,
nonInfoTooltipNonTouchShowDelay = 2 * mouseHoverTime,
infoTooltipNonTouchShowDelay = 2.5 * mouseHoverTime,
messageDuration = DEFAULT_MESSAGE_DURATION,
isLeftHanded = false;
var hasInitWinRTSettings = false;
var createEvent = WinJS.Utilities._createEventProperty;
return WinJS.Class.define(function Tooltip_ctor(anchorElement, options) {
///
///
/// Creates a new Tooltip.
///
///
/// The DOM element that hosts the Tooltip.
///
///
/// An object that contains one or more property/value pairs to apply to the new control.
/// Each property of the options object corresponds to one of the control's properties or events.
/// Event names must begin with "on". For example, to provide a handler for the opened event,
/// add a property named "onopened" to the options object and set its value to the event handler.
/// This parameter is optional.
///
///
/// The new Tooltip.
///
///
///
anchorElement = anchorElement || document.createElement("div");
var tooltip = utilities.data(anchorElement).tooltip;
if (tooltip) {
return tooltip;
}
// Set system attributes if it is in WWA, otherwise, use the default values
if (!hasInitWinRTSettings && WinJS.Utilities.hasWinRT) { // in WWA
var uiSettings = new Windows.UI.ViewManagement.UISettings();
mouseHoverTime = uiSettings.mouseHoverTime;
nonInfoTooltipNonTouchShowDelay = 2 * mouseHoverTime;
infoTooltipNonTouchShowDelay = 2.5 * mouseHoverTime;
messageDuration = uiSettings.messageDuration * 1000; // uiSettings.messageDuration is in seconds.
var handedness = uiSettings.handPreference;
isLeftHanded = (handedness == Windows.UI.ViewManagement.HandPreference.leftHanded);
}
hasInitWinRTSettings = true;
// Need to initialize properties
this._disposed = false;
this._placement = DEFAULT_PLACEMENT;
this._infotip = false;
this._innerHTML = null;
this._contentElement = null;
this._extraClass = null;
this._lastContentType = "html";
this._anchorElement = anchorElement;
this._domElement = null;
this._phantomDiv = null;
this._triggerByOpen = false;
this._eventListenerRemoveStack = [];
// To handle keyboard navigation
this._lastKeyOrBlurEvent = null;
this._currentKeyOrBlurEvent = null;
// Remember ourselves
anchorElement.winControl = this;
WinJS.Utilities.addClass(anchorElement, "win-disposable");
// If anchor element's title is defined, set as the default tooltip content
if (anchorElement.title) {
this._innerHTML = this._anchorElement.title;
this._anchorElement.removeAttribute("title");
}
WinJS.UI.setOptions(this, options);
this._events();
utilities.data(anchorElement).tooltip = this;
}, {
///
/// Gets or sets the HTML content of the Tooltip.
///
///
innerHTML: {
get: function () {
return this._innerHTML;
},
set: function (value) {
this._innerHTML = value;
if (this._domElement) {
// If we set the innerHTML to null or "" while tooltip is up, we should close it
if (!this._innerHTML || this._innerHTML === "") {
this._onDismiss();
return;
}
this._domElement.innerHTML = value;
this._position();
}
this._lastContentType = "html";
}
},
///
/// Gets or sets the DOM element that hosts the Tooltip.
///
///
element: {
get: function () {
return this._anchorElement;
}
},
///
/// Gets or sets the DOM element that is the content for the ToolTip.
///
///
contentElement: {
get: function () {
return this._contentElement;
},
set: function (value) {
this._contentElement = value;
if (this._domElement) {
// If we set the contentElement to null while tooltip is up, we should close it
if (!this._contentElement) {
this._onDismiss();
return;
}
this._domElement.innerHTML = "";
this._domElement.appendChild(this._contentElement);
this._position();
}
this._lastContentType = "element";
}
},
///
/// Gets or sets the position for the Tooltip relative to its target element: top, bottom, left or right.
///
///
placement: {
get: function () {
return this._placement;
},
set: function (value) {
if (value !== "top" && value !== "bottom" && value !== "left" && value !== "right") {
value = DEFAULT_PLACEMENT;
}
this._placement = value;
if (this._domElement) {
this._position();
}
}
},
///
/// Gets or sets a value that specifies whether the Tooltip is an infotip, a tooltip that contains
/// a lot of info and should be displayed for longer than a typical Tooltip.
/// The default value is false.
///
///
infotip: {
get: function () {
return this._infotip;
},
set: function (value) {
this._infotip = !!value; //convert the value to boolean
}
},
///
/// Gets or sets additional CSS classes to apply to the Tooltip control's host element.
///
///
extraClass: {
get: function () {
return this._extraClass;
},
set: function (value) {
this._extraClass = value;
}
},
///
/// Raised just before the Tooltip appears.
///
///
onbeforeopen: createEvent("beforeopen"),
///
/// Raised when the Tooltip is shown.
///
///
onopened: createEvent("opened"),
///
/// Raised just before the Tooltip is hidden.
///
///
onbeforeclose: createEvent("beforeclose"),
///
/// Raised when the Tooltip is no longer displayed.
///
///
onclosed: createEvent("closed"),
dispose: function () {
///
///
/// Disposes this Tooltip.
///
///
///
if (this._disposed) {
return;
}
this._disposed = true;
WinJS.Utilities.disposeSubTree(this.element);
for (var i = 0, len = this._eventListenerRemoveStack.length; i < len; i++) {
this._eventListenerRemoveStack[i]();
}
this._onDismiss();
var data = utilities.data(this._anchorElement);
if (data) {
delete data.tooltip;
}
},
addEventListener: function (eventName, eventCallBack, capture) {
///
///
/// Registers an event handler for the specified event.
///
/// The name of the event.
/// The event handler function to associate with this event.
/// Set to true to register the event handler for the capturing phase; set to false to register for the bubbling phase.
///
///
if (this._anchorElement) {
this._anchorElement.addEventListener(eventName, eventCallBack, capture);
var that = this;
this._eventListenerRemoveStack.push(function () {
that._anchorElement.removeEventListener(eventName, eventCallBack, capture);
});
}
},
removeEventListener: function (eventName, eventCallBack, capture) {
///
///
/// Unregisters an event handler for the specified event.
///
/// The name of the event.
/// The event handler function to remove.
/// Set to true to unregister the event handler for the capturing phase; otherwise, set to false to unregister the event handler for the bubbling phase.
///
///
if (this._anchorElement) {
this._anchorElement.removeEventListener(eventName, eventCallBack, capture);
}
},
open: function (type) {
///
///
/// Shows the Tooltip.
///
/// The type of tooltip to show: "touch", "mouseover", "mousedown", or "keyboard". The default value is "mousedown".
///
///
// Open takes precedence over other triggering events
// Once tooltip is opened using open(), it can only be closed by time out(mouseover or keyboard) or explicitly by close().
this._triggerByOpen = true;
if (type !== "touch" && type !== "mouseover" && type !== "mousedown" && type !== "keyboard") {
type = "default";
}
switch (type) {
case "touch":
this._onInvoke("touch", "never");
break;
case "mouseover":
this._onInvoke("mouse", "auto");
break;
case "keyboard":
this._onInvoke("keyboard", "auto");
break;
case "mousedown":
case "default":
this._onInvoke("nodelay", "never");
break;
}
},
close: function () {
///
///
/// Hids the Tooltip.
///
///
///
this._onDismiss();
},
_cleanUpDOM: function () {
if (this._domElement) {
WinJS.Utilities.disposeSubTree(this._domElement);
document.body.removeChild(this._domElement);
this._domElement = null;
document.body.removeChild(this._phantomDiv);
this._phantomDiv = null;
}
},
_createTooltipDOM: function () {
this._cleanUpDOM();
this._domElement = document.createElement("div");
var id = this._domElement.uniqueID;
this._domElement.setAttribute("id", id);
// Set the direction of tooltip according to anchor element's
var computedStyle = document.defaultView.getComputedStyle(this._anchorElement, null);
var elemStyle = this._domElement.style;
elemStyle.direction = computedStyle.direction;
elemStyle.writingMode = computedStyle["writing-mode"]; // must use CSS name, not JS name
// Make the tooltip non-focusable
this._domElement.setAttribute("tabindex", -1);
// Set the aria tags for accessibility
this._domElement.setAttribute("role", "tooltip");
this._anchorElement.setAttribute("aria-describedby", id);
// Set the tooltip content
if (this._lastContentType === "element") { // Last update through contentElement option
this._domElement.appendChild(this._contentElement);
} else { // Last update through innerHTML option
this._domElement.innerHTML = this._innerHTML;
}
document.body.appendChild(this._domElement);
utilities.addClass(this._domElement, msTooltip);
// In the event of user-assigned classes, add those too
if (this._extraClass) {
utilities.addClass(this._domElement, this._extraClass);
}
// Create a phantom div on top of the tooltip div to block all interactions
this._phantomDiv = document.createElement("div");
this._phantomDiv.setAttribute("tabindex", -1);
document.body.appendChild(this._phantomDiv);
utilities.addClass(this._phantomDiv, msTooltipPhantom);
var zIndex = document.defaultView.getComputedStyle(this._domElement, null).zIndex + 1;
this._phantomDiv.style.zIndex = zIndex;
},
_raiseEvent: function (type, eventProperties) {
if (this._anchorElement) {
var customEvent = document.createEvent("CustomEvent");
customEvent.initCustomEvent(type, false, false, eventProperties);
this._anchorElement.dispatchEvent(customEvent);
}
},
// Support for keyboard navigation
_captureLastKeyBlurOrPointerOverEvent: function (event, listener) {
listener._lastKeyOrBlurEvent = listener._currentKeyOrBlurEvent;
switch (event.type) {
case "keyup":
if (event.key === "Shift") {
listener._currentKeyOrBlurEvent = null;
} else {
listener._currentKeyOrBlurEvent = "keyboard";
}
break;
case "blur":
//anchor elment no longer in focus, clear up the stack
listener._currentKeyOrBlurEvent = null;
break;
default:
break;
}
},
_registerEventToListener: function (element, eventType, listener) {
var handler = function (event) {
listener._captureLastKeyBlurOrPointerOverEvent(event, listener);
listener._handleEvent(event);
};
element.addEventListener(eventType, handler, false);
this._eventListenerRemoveStack.push(function () {
element.removeEventListener(eventType, handler, false);
});
},
_events: function () {
for (var eventType in EVENTS_INVOKE) {
this._registerEventToListener(this._anchorElement, eventType, this);
}
for (var eventType in EVENTS_UPDATE) {
this._registerEventToListener(this._anchorElement, eventType, this);
}
for (eventType in EVENTS_DISMISS) {
this._registerEventToListener(this._anchorElement, eventType, this);
}
},
_handleEvent: function (event) {
var eventType = event.type;
if (!this._triggerByOpen) {
// If the anchor element has children, we should ignore events that are caused within the anchor element
// Please note that we are not using event.target here as in bubbling phases from the child, the event target
// is usually the child
if (eventType in EVENTS_BY_CHILD) {
var elem = event.relatedTarget;
while (elem && elem !== this._anchorElement && elem !== document.body) {
try {
elem = elem.parentNode;
}
catch (e) {
if (e instanceof Error && e.message === 'Permission denied') {
//Permission denied error, if we can't access the node's
//information, we should not handle the event
//Put this guard prior Bug 484666 is fixed
return;
}
else {
throw e;
}
}
}
if (elem === this._anchorElement) {
return;
}
}
if (eventType in EVENTS_INVOKE) {
if (event.pointerType == PT_TOUCH) {
this._onInvoke("touch", "never", event);
this._showTrigger = "touch";
} else {
var type = eventType.substring(0, 3) === "key" ? "keyboard" : "mouse";
this._onInvoke(type, "auto", event);
this._showTrigger = type;
}
} else if (eventType in EVENTS_UPDATE) {
this._contactPoint = { x: event.clientX, y: event.clientY };
} else if (eventType in EVENTS_DISMISS) {
var eventTrigger;
if (event.pointerType == PT_TOUCH) {
if (eventType == "pointerdown") {
return;
}
eventTrigger = "touch";
}
else {
eventTrigger = eventType.substring(0, 3) === "key" ? "keyboard" : "mouse";
}
if (eventType != "blur" && eventTrigger != this._showTrigger) {
return;
}
this._onDismiss();
}
}
},
_onShowAnimationEnd: function () {
if (this._shouldDismiss || this._disposed) {
return;
}
this._raiseEvent("opened");
if (this._domElement) {
if (this._hideDelay !== "never") {
var that = this;
var delay = this._infotip ? Math.min(3 * messageDuration, HIDE_DELAY_MAX) : messageDuration;
this._hideDelayTimer = setTimeout(function () {
that._onDismiss();
}, delay);
}
}
},
_onHideAnimationEnd: function () {
document.body.removeEventListener("DOMNodeRemoved", this._removeTooltip, false);
this._cleanUpDOM();
// Once we remove the tooltip from the DOM, we should remove the aria tag from the anchor
if (this._anchorElement) {
this._anchorElement.removeAttribute("aria-describedby");
}
lastCloseTime = (new Date()).getTime();
this._triggerByOpen = false;
if (!this._disposed) {
this._raiseEvent("closed");
}
},
_decideOnDelay: function (type) {
var value;
this._useAnimation = true;
if (type == "nodelay") {
value = 0;
this._useAnimation = false;
}
else {
var curTime = (new Date()).getTime();
// If the mouse is moved immediately from another anchor that has
// tooltip open, we should use a shorter delay
if (curTime - lastCloseTime <= RESHOW_THRESHOLD) {
if (type == "touch") {
value = this._infotip ? DELAY_RESHOW_INFOTIP_TOUCH : DELAY_RESHOW_NONINFOTIP_TOUCH;
}
else {
value = this._infotip ? DELAY_RESHOW_INFOTIP_NONTOUCH : DELAY_RESHOW_NONINFOTIP_NONTOUCH;
}
this._useAnimation = false;
} else if (type == "touch") {
value = this._infotip ? DELAY_INITIAL_TOUCH_LONG : DELAY_INITIAL_TOUCH_SHORT;
} else {
value = this._infotip ? infoTooltipNonTouchShowDelay : nonInfoTooltipNonTouchShowDelay;
}
}
return value;
},
// This function returns the anchor element's position in the Window coordinates.
_getAnchorPositionFromElementWindowCoord: function () {
var rect = this._anchorElement.getBoundingClientRect();
return {
x: rect.left,
y: rect.top,
width: rect.width,
height: rect.height
};
},
_getAnchorPositionFromPointerWindowCoord: function (contactPoint) {
return {
x: contactPoint.x,
y: contactPoint.y,
width: 1,
height: 1
};
},
_canPositionOnSide: function (placement, viewport, anchor, tip) {
var availWidth = 0, availHeight = 0;
switch (placement) {
case "top":
availWidth = tip.width + this._offset;
availHeight = anchor.y;
break;
case "bottom":
availWidth = tip.width + this._offset;
availHeight = viewport.height - anchor.y - anchor.height;
break;
case "left":
availWidth = anchor.x;
availHeight = tip.height + this._offset;
break;
case "right":
availWidth = viewport.width - anchor.x - anchor.width;
availHeight = tip.height + this._offset;
break;
}
return ((availWidth >= tip.width + this._offset) && (availHeight >= tip.height + this._offset));
},
_positionOnSide: function (placement, viewport, anchor, tip) {
var left = 0, top = 0;
switch (placement) {
case "top":
case "bottom":
// Align the tooltip to the anchor's center horizontally
left = anchor.x + anchor.width / 2 - tip.width / 2;
// If the left boundary is outside the window, set it to 0
// If the right boundary is outside the window, set it to align with the window right boundary
left = Math.min(Math.max(left, 0), viewport.width - tip.width - SAFETY_NET_GAP);
top = (placement == "top") ? anchor.y - tip.height - this._offset : anchor.y + anchor.height + this._offset;
break;
case "left":
case "right":
// Align the tooltip to the anchor's center vertically
top = anchor.y + anchor.height / 2 - tip.height / 2;
// If the top boundary is outside the window, set it to 0
// If the bottom boundary is outside the window, set it to align with the window bottom boundary
top = Math.min(Math.max(top, 0), viewport.height - tip.height - SAFETY_NET_GAP);
left = (placement == "left") ? anchor.x - tip.width - this._offset : anchor.x + anchor.width + this._offset;
break;
}
// Actually set the position
this._domElement.style.left = left + "px";
this._domElement.style.top = top + "px";
// Set the phantom's position and size
this._phantomDiv.style.left = left + "px";
this._phantomDiv.style.top = top + "px";
this._phantomDiv.style.width = tip.width + "px";
this._phantomDiv.style.height = tip.height + "px";
},
_position: function (contactType) {
var viewport = { width: 0, height: 0 };
var anchor = { x: 0, y: 0, width: 0, height: 0 };
var tip = { width: 0, height: 0 };
viewport.width = document.documentElement.clientWidth;
viewport.height = document.documentElement.clientHeight;
if (document.defaultView.getComputedStyle(document.body, null)["writing-mode"] === "tb-rl") {
viewport.width = document.documentElement.clientHeight;
viewport.height = document.documentElement.clientWidth;
}
if (this._contactPoint && (contactType === "touch" || contactType === "mouse")) {
anchor = this._getAnchorPositionFromPointerWindowCoord(this._contactPoint);
}
else {
// keyboard or programmatic is relative to element
anchor = this._getAnchorPositionFromElementWindowCoord();
}
tip.width = this._domElement.offsetWidth;
tip.height = this._domElement.offsetHeight;
var fallback_order = {
"top": ["top", "bottom", "left", "right"],
"bottom": ["bottom", "top", "left", "right"],
"left": ["left", "right", "top", "bottom"],
"right": ["right", "left", "top", "bottom"]
};
if (isLeftHanded) {
fallback_order.top[2] = "right";
fallback_order.top[3] = "left";
fallback_order.bottom[2] = "right";
fallback_order.bottom[3] = "left";
}
// Try to position the tooltip according to the placement preference
// We use this order:
// 1. Try the preferred placement
// 2. Try the opposite placement
// 3. If the preferred placement is top or bottom, we should try left
// and right (or right and left if left handed)
// If the preferred placement is left or right, we should try top and bottom
var order = fallback_order[this._placement];
var length = order.length;
for (var i = 0; i < length; i++) {
if (i == length - 1 || this._canPositionOnSide(order[i], viewport, anchor, tip)) {
this._positionOnSide(order[i], viewport, anchor, tip);
break;
}
}
return order[i];
},
_showTooltip: function (contactType) {
// Give a chance to dismiss the tooltip before it starts to show
if (this._shouldDismiss) {
return;
}
this._isShown = true;
this._raiseEvent("beforeopen");
// If the anchor is not in the DOM tree, we don't create the tooltip
if (!document.body.contains(this._anchorElement)) {
return;
}
if (this._shouldDismiss) {
return;
}
// If the contentElement is set to null or innerHTML set to null or "", we should NOT show the tooltip
if (this._lastContentType === "element") { // Last update through contentElement option
if (!this._contentElement) {
this._isShown = false;
return;
}
} else { // Last update through innerHTML option
if (!this._innerHTML || this._innerHTML === "") {
this._isShown = false;
return;
}
}
var that = this;
this._removeTooltip = function (event) {
var current = that._anchorElement;
while (current) {
if (event.target == current) {
document.body.removeEventListener("DOMNodeRemoved", that._removeTooltip, false);
that._cleanUpDOM();
break;
}
current = current.parentNode;
}
};
document.body.addEventListener("DOMNodeRemoved", this._removeTooltip, false);
this._createTooltipDOM();
var pos = this._position(contactType);
var that = this;
if (this._useAnimation) {
animation.fadeIn(this._domElement)
.then(this._onShowAnimationEnd.bind(this));
} else {
this._onShowAnimationEnd();
}
},
_onInvoke: function (type, hide, event) {
// Reset the dismiss flag
this._shouldDismiss = false;
// If the tooltip is already shown, ignore the current event
if (this._isShown) {
return;
}
// To handle keyboard support, we only want to display tooltip on the first tab key event only
if (event && event.type === "keyup") {
if (this._lastKeyOrBlurEvent == "keyboard" ||
!this._lastKeyOrBlurEvent && event.key !== "Tab") {
return;
}
}
// Set the hide delay,
this._hideDelay = hide;
this._contactPoint = null;
if (event) { // Open through interaction
this._contactPoint = { x: event.clientX, y: event.clientY };
// Tooltip display offset differently for touch events and non-touch events
if (type == "touch") {
this._offset = OFFSET_TOUCH;
} else if (type === "keyboard") {
this._offset = OFFSET_KEYBOARD;
} else {
this._offset = OFFSET_MOUSE;
}
} else { // Open Programmatically
if (type == "touch") {
this._offset = OFFSET_PROGRAMMATIC_TOUCH;
} else {
this._offset = OFFSET_PROGRAMMATIC_NONTOUCH;
}
}
clearTimeout(this._delayTimer);
clearTimeout(this._hideDelayTimer);
// Set the delay time
var delay = this._decideOnDelay(type);
if (delay > 0) {
var that = this;
this._delayTimer = setTimeout(function () {
that._showTooltip(type);
}, delay);
} else {
this._showTooltip(type);
}
},
_onDismiss: function () {
// Set the dismiss flag so that we don't miss dismiss events
this._shouldDismiss = true;
// If the tooltip is already dismissed, ignore the current event
if (!this._isShown) {
return;
}
this._isShown = false;
// Reset tooltip state
this._showTrigger = "mouse";
if (this._domElement) {
this._raiseEvent("beforeclose");
if (this._useAnimation) {
animation.fadeOut(this._domElement)
.then(this._onHideAnimationEnd.bind(this));
} else {
this._onHideAnimationEnd();
}
} else {
this._raiseEvent("beforeclose");
this._raiseEvent("closed");
}
}
});
})
});
})(this, WinJS);
// ViewBox control
(function viewboxInit(global, undefined) {
"use strict";
WinJS.Namespace.define("WinJS.UI", {
///
/// Scales a single child element to fill the available space without
/// resizing it. This control reacts to changes in the size of the container as well as
/// changes in size of the child element. For example, a media query may result in
/// a change in aspect ratio.
///
/// View Box
///
///
///
ViewBox
]]>
///
///
///
ViewBox: WinJS.Namespace._lazy(function () {
var Scheduler = WinJS.Utilities.Scheduler;
var strings = {
get invalidViewBoxChildren() { return WinJS.Resources._getWinJSString("ui/invalidViewBoxChildren").value; },
};
function onresize(control) {
if (control && !control._resizing) {
control._resizing = control._resizing || 0;
control._resizing++;
try {
control._updateLayout();
} finally {
control._resizing--;
}
}
}
function onresizeBox(ev) {
if (ev.srcElement) {
onresize(ev.srcElement.winControl);
}
}
function onresizeSizer(ev) {
if (ev.srcElement) {
onresize(ev.srcElement.parentElement.winControl);
}
}
var ViewBox = WinJS.Class.define(function ViewBox_ctor(element, options) {
///
/// Initializes a new instance of the ViewBox control
///
/// The DOM element that functions as the scaling box. This element fills 100% of the width and height allotted to it.
///
///
/// The set of options to be applied initially to the ViewBox control.
///
/// A constructed ViewBox control.
///
this._disposed = false;
this._element = element || document.createElement("div");
var box = this.element;
box.winControl = this;
WinJS.Utilities.addClass(box, "win-disposable");
WinJS.Utilities.addClass(box, "win-viewbox");
this.forceLayout();
}, {
_sizer: null,
_element: null,
///
/// Gets the DOM element that functions as the scaling box.
///
element: {
get: function () { return this._element; }
},
_rtl: {
get: function () {
return window.getComputedStyle(this.element).direction === "rtl";
}
},
_initialize: function () {
var box = this.element;
if (box.firstElementChild !== this._sizer) {
if (WinJS.validation) {
if (box.childElementCount != 1) {
throw new WinJS.ErrorFromName("WinJS.UI.ViewBox.InvalidChildren", strings.invalidViewBoxChildren);
}
}
if (this._sizer) {
this._sizer.onresize = null;
}
var sizer = box.firstElementChild;
this._sizer = sizer;
if (sizer) {
box.addEventListener("mselementresize", onresizeBox);
sizer.addEventListener("mselementresize", onresizeSizer);
}
if (box.clientWidth === 0 && box.clientHeight === 0) {
var that = this;
// Wait for the viewbox to get added to the DOM. It should be added
// in the synchronous block in which _initialize was called.
Scheduler.schedule(function () {
that._updateLayout();
}, Scheduler.Priority.normal, null, "WinJS.UI.ViewBox._updateLayout")
}
}
},
_updateLayout: function () {
var sizer = this._sizer;
if (sizer) {
var box = this.element;
var w = sizer.clientWidth;
var h = sizer.clientHeight;
var bw = box.clientWidth;
var bh = box.clientHeight;
var wRatio = bw / w;
var hRatio = bh / h;
var mRatio = Math.min(wRatio, hRatio);
var transX = Math.abs(bw - (w * mRatio)) / 2;
var transY = Math.abs(bh - (h * mRatio)) / 2;
var rtl = this._rtl;
this._sizer.style["transform"] = "translate(" + (rtl ? "-" : "") + transX + "px," + transY + "px) scale(" + mRatio + ")";
this._sizer.style["transform-origin"] = rtl ? "top right" : "top left";
}
},
dispose: function () {
///
///
/// Disposes this ViewBox.
///
///
if (this._disposed) {
return;
}
this._disposed = true;
WinJS.Utilities.disposeSubTree(this._element);
},
forceLayout: function () {
this._initialize();
this._updateLayout();
}
});
WinJS.Class.mix(ViewBox, WinJS.UI.DOMEventMixin);
return ViewBox;
})
});
}(this));
msWriteProfilerMark("Microsoft.WinJS.2.0 1.0.9600.17018.winblue_gdr.140204-1946 ui.js,StopTM");