This repository has been archived on 2026-05-12. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
PaintPlay/JS/CommandBar/CommandBar.js
adrianvic 59426d62fb first
2023-12-21 14:33:34 -03:00

1821 lines
No EOL
75 KiB
JavaScript

////////////////////////////////////////////////////////////
//// © Microsoft. All rights reserved. ////
////////////////////////////////////////////////////////////
/// <reference path="WinLib/Js/base.js" />
/// <reference path="WinLib/Js/controls.js" />
/// <reference path="WinLib/Js/ui.js" />
/// <reference path="Flyout.js" />
(function (WinNS) {
var Utilities = WinNS.Utilities;
var Animation = WinNS.UI.Animation;
var Controls = WinNS.MediaApp.Controls;
var Promise = WinNS.Promise;
var elementIsInvalid = "Invalid argument: Control expects a valid DOM element as the first argument.";
var missingParameter = "Required argument missing or wrong type: ";
var tokenPattern = /{{(\w+)}}/g;
var groupDescribedById = "groupDescribedBy_{0}_{1}";
var commandDescribedById = "commandDescribedBy_{0}_{1}";
var groupElementId = "commandGroup_{0}_{1}";
var commandElementId = "command_{0}_{1}";
var flyoutElementId = "flyout_{0}";
var formatString = function (str, /*@type(Array)*/args) {
if (!Array.isArray(args)) {
throw new Error("Invalid argument: Expect 'args' to be an array");
}
return str.replace(/{(\d+)}/g, function (placeHolder, index) {
var token = args[index];
if (token === undefined) {
token = placeHolder;
} else if (token === null) {
token = "";
}
return token;
});
};
var classes = {
commandBar: "win-commandBar",
commandBarHoriz: "win-commandBar-horiz",
commandBarVert: "win-commandBar-vert",
commandStackHoriz: "win-commandBar-stack-horiz",
commandStackVert: "win-commandBar-stack-vert",
commandStackTest: "win-commandBar-stack-test",
commandGroupHoriz: "win-commandBar-group-horiz",
commandGroupVert: "win-commandBar-group-vert",
commandHoriz: "win-commandBar-command-horiz",
commandVert: "win-commandBar-command-vert",
commandHostedHoriz: "win-commandBar-hostedCommand-horiz",
commandHostedVert: "win-commandBar-hostedCommand-vert",
separatorHoriz: "win-commandBar-rule-horiz",
separatorVert: "win-commandBar-rule-vert"
};
var classSuffixHoriz = "Horiz";
var classSuffixVert = "Vert";
var WinUI = WinNS.Namespace.defineWithParent(WinNS, "UI", {
CommandBarOrientation: {
horizontal: "horizontal",
vertical: "vertical"
},
CommandStack: {
left: "left",
right: "right",
expansion: "expansion"
},
CommandBar: WinNS.Class.define(function CommandBar_ctor(element, options) {
/// <summary>
/// Creates an instance of the CommandBar control
/// </summary>
/// <param name='element'>
/// The DOM element that will host the control
/// </param>
/// <param name='options'>
/// An object literal that defines option values for the control
/// </param>
/// <returns type="WinJS.UI.CommandBar" />
if (!element) {
throw new Error(elementIsInvalid);
}
// Configure host element
if (this === window || this === WinUI) {
var commandBar = WinUI.getControl(element);
if (commandBar) {
return commandBar;
} else {
return new WinUI.CommandBar(element, options);
}
}
// Attach the JS object to the host DOM element.
WinUI.setControl(element, this);
this._element = element;
var that = this;
if (options.uniqueId) {
this._uniqueId = options.uniqueId;
} else {
var ms = new Date();
ms = ms.valueOf().toString();
this._uniqueId = ms.substr(ms.length - 5, 5);
}
var ariaLabel = options.ariaLabel || "Command bar";
this.element.role = "menubar";
this.element.setAttribute("aria-label", ariaLabel);
this.element.setAttribute("uniqueId", this._uniqueId);
Utilities.addClass(this.element, classes.commandBar);
// If we are contained within an AppBar, store a reference
if (this.element.parentNode) {
if (Utilities.hasClass(this.element.parentNode, "win-appbar")) {
this._parentAppBarElement = this.element.parentNode;
this._parentAppBar = WinUI.getControl(this._parentAppBarElement);
if (this._parentAppBar && this._parentAppBar.transient) {
// Dismiss the expansion flyout when the AppBar hides
this._parentAppBar.addEventListener("beforehide", function () {
that.hideFlyout();
}, false);
}
}
}
// Create the default horizontal command template
var buttonHoriz = document.createElement("button");
buttonHoriz.id = formatString(commandElementId, [this._uniqueId, "{{id}}"]);
buttonHoriz.type = "button";
buttonHoriz.title = "{{commandName}}";
buttonHoriz.accessKey = "{{accessKey}}";
buttonHoriz.setAttribute("role", "menuitem");
buttonHoriz.setAttribute("aria-label", "{{commandName}}");
buttonHoriz.setAttribute("aria-describedby", formatString(commandDescribedById, [this._uniqueId, "{{id}}"]));
var image = document.createElement("img");
image.setAttribute("aria-hidden", true);
image.setAttribute("data-win-template", "{{imageUrl}}");
Utilities.addClass(image, "commandIcon");
var text = document.createElement("div");
text.setAttribute("data-win-minimize", true);
text.innerText = "{{commandName}}";
Utilities.addClass(text, "commandLabel");
Utilities.addClass(text, "typeEllipsis");
buttonHoriz.appendChild(image);
buttonHoriz.appendChild(text);
// Create the default vertical command template
var buttonVert = document.createElement("button");
buttonVert.id = formatString(commandElementId, [this._uniqueId, "{{id}}"]);
buttonVert.type = "button";
buttonVert.title = "{{commandName}}";
buttonVert.accessKey = "{{accessKey}}";
buttonVert.innerText = "{{commandName}}";
buttonVert.setAttribute("role", "listitem");
buttonVert.setAttribute("aria-label", "{{commandName}}");
buttonVert.setAttribute("aria-describedby", formatString(commandDescribedById, [this._uniqueId, "{{id}}"]));
// Create the tooltip template
var tooltip = document.createElement("div");
tooltip.id = formatString(commandElementId, [this._uniqueId, "{{id}}_tooltip"]);
tooltip.setAttribute("aria-label", "{{title}}");
Utilities.addClass(tooltip, "win-commandBar-tooltip");
Utilities.addClass(tooltip, "typeSizeSmall");
Utilities.addClass(tooltip, "typeFaceNormal");
var tooltipHeading = document.createElement("div");
Utilities.addClass(tooltipHeading, "win-commandBar-tooltip-heading");
var tooltipTitle = document.createElement("div");
tooltipTitle.innerText = "{{title}}";
Utilities.addClass(tooltipTitle, "win-commandBar-tooltip-title");
Utilities.addClass(tooltipTitle, "colorPrimaryLightBG");
Utilities.addClass(tooltipTitle, "typeEllipsis");
var tooltipShortcut = document.createElement("div");
tooltipShortcut.innerText = "{{shortcut}}";
Utilities.addClass(tooltipShortcut, "win-commandBar-tooltip-shortcut");
Utilities.addClass(tooltipShortcut, "colorPrimaryLightBG");
var tooltipFlex = document.createElement("div");
Utilities.addClass(tooltipFlex, "win-commandBar-tooltip-flex");
var tooltipDesc = document.createElement("div");
tooltipDesc.innerText = "{{description}}";
Utilities.addClass(tooltipDesc, "win-commandBar-tooltip-description");
Utilities.addClass(tooltipDesc, "colorSecondaryLightBG");
tooltipHeading.appendChild(tooltipTitle);
tooltipHeading.appendChild(tooltipShortcut);
tooltipHeading.appendChild(tooltipFlex);
tooltip.appendChild(tooltipHeading);
tooltip.appendChild(tooltipDesc);
this._tooltipTemplate = tooltip;
// Set options
this._orientation = options.orientation || WinUI.CommandBarOrientation.horizontal;
this._commandTemplateHoriz = options.commandTemplateHoriz || buttonHoriz;
this._commandTemplateVert = options.commandTemplateVert || buttonVert;
this._tooltipShortcutFormat = options.tooltipShortcutFormat || "({0})";
this._visible = options.visible || true;
this._minimized = false;
if (options.minimized) {
this.minimized = options.minimized;
}
this._currentCommandSet = [];
this._groupsCache = [];
this._expandoCommand = null;
// Create a div to hold custom elements
var customElementsHost = document.createElement("div");
customElementsHost.style.display = "none";
this._customElementsHost = customElementsHost;
this.element.appendChild(customElementsHost);
// Create a div to hold the ARIA describedby elements
var describedByHost = document.createElement("div");
describedByHost.style.display = "none";
this._describedByHost = describedByHost;
this.element.appendChild(describedByHost);
// Create stack data structures
this._commandStacks = {};
// Create the expansion stack
this._commandStacks[WinUI.CommandStack.expansion] = {};
this._commandStacks[WinUI.CommandStack.expansion].groups = [];
var expansionStack = document.createElement("div");
expansionStack.className = classes.commandStackVert;
expansionStack.style.display = this.orientation === WinUI.CommandBarOrientation.vertical ? "block" : "none";
expansionStack.setAttribute("stack", WinUI.CommandStack.expansion);
this._commandStacks[WinUI.CommandStack.expansion].element = expansionStack;
if (this.orientation === WinUI.CommandBarOrientation.horizontal) {
Utilities.addClass(this.element, classes.commandBarHoriz);
this._commandStacks[WinUI.CommandStack.left] = {};
this._commandStacks[WinUI.CommandStack.right] = {};
this._commandStacks["test"] = {};
this._commandStacks[WinUI.CommandStack.left].groups = [];
this._commandStacks[WinUI.CommandStack.right].groups = [];
this._commandStacks["test"].groups = [];
var leftStack = document.createElement("div");
var rightStack = document.createElement("div");
var testStack = document.createElement("div");
leftStack.className = rightStack.className = classes.commandStackHoriz;
testStack.className = classes.commandStackTest;
leftStack.setAttribute("stack", WinUI.CommandStack.left);
rightStack.setAttribute("stack", WinUI.CommandStack.right);
testStack.setAttribute("stack", "test");
this._commandStacks[WinUI.CommandStack.left].element = leftStack;
this._commandStacks[WinUI.CommandStack.right].element = rightStack;
this._commandStacks["test"].element = testStack;
// The test stack is used to test for overflow as each item is added to the horizontal stacks
this._testStack = testStack;
this._overflowedCommands = [];
this._expandoShowing = false;
// Create the expansion flyout
var flyoutHost = document.createElement("div");
flyoutHost.id = formatString(flyoutElementId, [this._uniqueId]);
Utilities.addClass(flyoutHost, "win-commandBar-flyout");
document.body.appendChild(flyoutHost);
this._flyoutHost = flyoutHost;
this._flyout = new WinUI.Flyout(flyoutHost, null);
this._flyout.addEventListener("aftershow", function () {
that._fireEvent("expansioninvoked", null);
}, false);
this._flyout.addEventListener("afterhide", function () {
var command = that._expandoCommand.msDataItem;
if (command && command.states.length > 1) {
that.setCommandState(command.id, 0);
} else {
that._expandoCommand.focus();
}
that._expandoShowing = false;
that._fireEvent("expansiondismissed", null);
}, false);
} else {
Utilities.addClass(this.element, classes.commandBarVert);
}
// Put the stack elements in the host div
Object.keys(this._commandStacks).forEach(function (stack) {
that.element.appendChild(that._commandStacks[stack].element);
});
// This must be done after the stacks are ready
this.commandGroups = options.commandGroups;
},
{
// Public properties
element: {
/// <summary>
/// Gets the DOM element that hosts this CommandBar instance
/// </summary>
get: function () {
return this._element;
}
},
orientation: {
/// <summary>
/// Gets the orientation of this CommandBar instance
/// </summary>
get: function () {
return this._orientation;
}
},
commandTemplateHoriz: {
/// <summary>
/// Gets or sets the horizontal command template of this CommandBar instance
/// </summary>
get: function () {
return this._commandTemplateHoriz;
},
set: function (value) {
if (typeof value === "object") {
this._commandTemplateHoriz = value;
this._commandTemplateHoriz.id = formatString(commandElementId, [this._uniqueId, "{{id}}"]);
}
}
},
commandTemplateVert: {
/// <summary>
/// Gets or sets the vertical command template of this CommandBar instance
/// </summary>
get: function () {
return this._commandTemplateVert;
},
set: function (value) {
if (typeof value === "object") {
this._commandTemplateVert = value;
this._commandTemplateVert.id = formatString(commandElementId, [this._uniqueId, "{{id}}"]);
}
}
},
commandGroups: {
/// <summary>
/// Gets or sets the CommandGroups hosted by this CommandBar instance. This resets the CommandBar's UI.
/// </summary>
get: function () {
return this._groupsCache;
},
set: function (value) {
if (Array.isArray(value) && value.length > 0) {
this._groupsCache = value;
var that = this;
Object.keys(this._commandStacks).forEach(function (stack) {
// Clear each stack's groups array
that._commandStacks[stack].groups.splice(0);
});
// Clear all DOM elements
this._clear();
value.forEach(function (group) {
// Insert each group into the appropriate stack
that._commandStacks[group.commandStack].groups.push(group);
// Create an ARIA describedBy element
var div = document.createElement("div");
div.id = formatString(groupDescribedById, [that._uniqueId, group.id]);
div.role = "note";
div.innerText = group.description;
that._describedByHost.appendChild(div);
group.commands.forEach(function (command) {
// Create an ARIA describedBy element
var div = document.createElement("div");
div.id = formatString(commandDescribedById, [that._uniqueId, command.id]);
div.role = "note";
div.innerText = command.description;
that._describedByHost.appendChild(div);
});
});
}
}
},
visible: {
/// <summary>
/// Gets or sets the visibility of this CommandBar instance
/// </summary>
get: function () {
return this._visible;
},
set: function (value) {
if (typeof value === "boolean" && this._visible !== value) {
this._visible = value;
this.element.style.visibility = value ? "visible" : "hidden";
this._fireEvent("visibilitychanged", null);
}
}
},
minimized: {
/// <summary>
/// Gets or sets the minimized state of this CommandBar instance
/// </summary>
get: function () {
return this._minimized;
},
set: function (value) {
if (typeof value === "boolean" && this._minimized !== value) {
this._minimized = value;
Utilities[this._minimized ? "addClass" : "removeClass"](this.element, "minimized");
this._fireEvent("minimizechanged", null);
}
}
},
// Public methods
addEventListener: function MediaQueue_addEventListener(eventName, eventCallback, capture) {
/// <summary>
/// Registers an event handler for the specified event type
/// </summary>
/// <param name='eventName'>
/// The type of event type to register
/// </param>
/// <param name='eventCallback'>
/// The event handler function to associate with the event
/// </param>
/// <param name='capture'>
/// A Boolean value that specifies the event phase to add the event handler for:
/// true
/// Register the event handler for the capturing phase
/// false
/// Register the event handler for the bubbling phase
/// </param>
/// <returns>
/// None
/// </returns>
this.element.addEventListener(eventName, eventCallback, capture);
},
removeEventListener: function MediaQueue_removeEventListener(eventName, eventCallback, capture) {
/// <summary>
/// Removes an event handler that the addEventListener method registered
/// </summary>
/// <param name='eventName'>
/// The event type that the event handler is registered for
/// </param>
/// <param name='eventCallback'>
/// The event handler function to remove
/// </param>
/// <param name='capture'>
/// A Boolean value that specifies the event phase to add the event handler for:
/// true
/// Remove the capturing phase event handler
/// false
/// Remove the bubbling phase event handler
/// </param>
/// <returns>
/// None
/// </returns>
this.element.removeEventListener(eventName, eventCallback, capture);
},
show: function CommandBar_show() {
/// <summary>
/// Shows the command bar, if it is hosted in an AppBar
/// </summary>
/// <returns>
/// None
/// </returns>
if (this._parentAppBar && this._parentAppBar.transient) {
this._parentAppBar.show();
this._fireEvent("show", null);
}
},
hide: function CommandBar_hide() {
/// <summary>
/// Hides the command bar, if it is hosted in an AppBar
/// </summary>
/// <returns>
/// None
/// </returns>
if (this._parentAppBar && this._parentAppBar.transient) {
this.hideFlyout();
this._parentAppBar.hide();
this._fireEvent("hide", null);
}
},
refresh: function CommandBar_refresh() {
this._clear();
return this.setCommands(this._currentCommandSet);
},
setCommands: function CommandBar_setCommands(commandNames) {
/// <summary>
/// Triggers rendering of the command bar with the specified commands
/// </summary>
/// <param name='commandNames'>
/// A string array specifying the names of the commands to be rendered
/// </param>
/// <returns>
/// A Promise that indicates completion of rendering
/// </returns>
return this._render(commandNames);
},
showCommands: function CommandBar_showCommands(commandNames) {
/// <summary>
/// Shows the specified commands in addition the currently visible set
/// </summary>
/// <param name='commandNames'>
/// A string or array of strings specifying the names of the commands to be shown
/// </param>
/// <returns>
/// A Promise that indicates completion of rendering
/// </returns>
var commands = null;
if (Array.isArray(commandNames)) {
commands = commandNames;
} else if (typeof commandNames === "string") {
commands = [commandNames];
}
if (commands) {
var newSet = this._currentCommandSet.slice(0);
var that = this;
commands.forEach(function (commandName) {
var index = newSet.indexOf(commandName);
if (index === -1) {
newSet.push(commandName);
}
});
return this._render(newSet);
} else {
return Promise.wrap();
}
},
hideCommands: function CommandBar_hideCommands(commandNames) {
/// <summary>
/// Hides the specified command
/// </summary>
/// <param name='commandNames'>
/// A string or array of strings specifying the names of the commands to be hidden
/// </param>
/// <returns>
/// A Promise that indicates completion of rendering
/// </returns>
var commands = null;
if (Array.isArray(commandNames)) {
commands = commandNames;
} else if (typeof commandNames === "string") {
commands = [commandNames];
}
if (commands) {
var newSet = this._currentCommandSet.slice(0);
var that = this;
commands.forEach(function (commandName) {
var index = newSet.indexOf(commandName);
if (index > -1) {
newSet.splice(index, 1);
}
});
return this._removeHiddenCommands(newSet)
.then(function () {
that._setAppBarTransparency();
});
} else {
return Promise.wrap();
}
},
invokeCommand: function CommandBar_invokeCommand(commandId) {
/// <summary>
/// Invokes the specified command
/// </summary>
/// <param name='commandId'>
/// A string identifier of the command to be invoked
/// </param>
/// <returns>
/// None
/// </returns>
var commandElement = this._findCommandElement(commandId);
if (commandElement) {
commandElement.fireEvent("onclick");
}
},
setCommandState: function CommandBar_setCommandState(commandId, state) {
/// <summary>
/// Sets the state of the specified command, which takes effect immediately
/// </summary>
/// <param name='commandId'>
/// A string identifier of the command to be updated
/// </param>
/// <param name='state'>
/// The new command state
/// </param>
/// <returns>
/// None
/// </returns>
var commandElement = this._findCommandElement(commandId);
if (commandElement) {
var command = commandElement.msDataItem;
this._changeCommandState(command, commandElement, state);
}
},
setLayoutAttribute: function CommandBar_setLayoutAttribute(layout) {
/// <summary>
/// Sets the appLayout attribute value on the control element
/// </summary>
/// <param name='layout'>
/// The value to be set in the appLayout attribute
/// </param>
/// <returns>
/// None
/// </returns>
this.element.setAttribute("appLayout", layout);
},
hideFlyout: function CommandBar_hideFlyout() {
/// <summary>
/// Dismisses the expansion flyout
/// </summary>
/// <returns>
/// None
/// </returns>
if (this._flyout && this._flyout.visible) {
this._flyout.hide();
}
},
// Private methods
// Raises an event to external listeners with the specified name and properties
_fireEvent: function CommandBar_fireEvent(eventName, properties) {
if (document.createEvent) {
var eventObject = document.createEvent("Event");
eventObject.initEvent(eventName, true, false);
if (properties) {
Object.keys(properties).forEach(function (prop) {
eventObject[prop] = properties[prop];
});
}
this.element.dispatchEvent(eventObject);
}
},
// Handles command invocation and state transition
_commandInvoked: function CommandBar_commandInvoked(event) {
var command = event.srcElement.msDataItem;
var isExpando = event.srcElement.hasAttribute("isExpando");
var stack = event.srcElement.parentNode.getAttribute("stack");
var state = Number(event.srcElement.getAttribute("state"));
this._fireEvent("commandinvoked", { payload: command.states[state].payload, cmdElement: event.srcElement });
if (command.states.length > 1 && command.advanceState) {
var nextState = state === command.states.length - 1 ? 0 : state + 1;
command.initialState = nextState;
this._changeCommandState(command, event.srcElement, nextState);
}
if (isExpando) {
if (!this._expandoShowing) {
this._showExpando(event.srcElement, stack);
}
} else if (command.hideOnInvoke) {
if (stack === WinUI.CommandStack.expansion) {
this.hideFlyout();
} else {
this.hide();
}
}
},
// Handles command focus
_commandFocused: function CommandBar_commandFocused(event) {
var command = event.srcElement.msDataItem;
var state = Number(event.srcElement.getAttribute("state"));
this._fireEvent("commandfocused", { payload: state ? command.states[state].payload : null });
},
// Displays the expansion stack
_showExpando: function CommandBar_showExpando(sourceCommand, stack) {
var stackElement = this._commandStacks[WinUI.CommandStack.expansion].element;
stackElement.style.display = "block";
Utilities.empty(this._flyoutHost);
this._flyoutHost.appendChild(stackElement);
this._expandoCommand = sourceCommand;
this._expandoShowing = true;
this._flyout.show(this._expandoCommand, "top");
},
// Clears all DOM elements from the stacks
_clear: function CommandBar_clear() {
var that = this;
Object.keys(this._commandStacks).forEach(function (stack) {
Utilities.empty(that._commandStacks[stack].element);
});
Utilities.empty(this._describedByHost);
},
// Changes the state of the specified command
_changeCommandState: function CommandBar_changeCommandState(command, commandElement, nextState) {
var stack = commandElement.parentNode.getAttribute("stack");
var classSuffix = stack === WinUI.CommandStack.expansion ? classSuffixVert : classSuffixHoriz;
var element = this._renderCommand(command, nextState, classSuffix);
commandElement.insertAdjacentElement("beforeBegin", element);
commandElement.removeNode(true);
element.focus();
this._fireEvent("commandstatechanged", { payload: command.states[nextState].payload, cmdElement: commandElement });
},
// Renders the specified set of commands
_render: function CommandBar_render(commandNames) {
// Create a priority-ranked mapping between commands and groups
this._commandMap = this._buildCommandMap(commandNames);
var promise = null;
if (this.orientation === WinUI.CommandBarOrientation.horizontal) {
promise = this._renderHorizontal(commandNames);
} else {
promise = this._renderVertical(commandNames);
}
this._currentCommandSet = commandNames;
this._setAppBarTransparency();
return promise;
},
// Renders the command bar horizontally
_renderHorizontal: function CommandBar_renderHorizontal(commandNames) {
var that = this;
var testWidth = 0;
return this._removeHiddenCommands(commandNames)
.then(function (count) {
var willElementOverflow = function (element, showSeparator) {
var leftWidth = that._commandStacks[WinUI.CommandStack.left].element.offsetWidth;
var rightWidth = that._commandStacks[WinUI.CommandStack.right].element.offsetWidth;
// If the element has a parent, it has been tested before
// This is needed for the overflow loop below.
if (!element.parentNode) {
// Create a group in the test stack
var groupHost = document.createElement("div");
groupHost.className = classes.commandGroupHoriz;
groupHost.setAttribute("stack", "test");
that._testStack.appendChild(groupHost);
groupHost.appendChild(element);
if (showSeparator) {
that._renderSeparator(groupHost);
}
}
// Add the width of the new group + command to the total
testWidth += Utilities.getTotalWidth(element.parentNode);
return leftWidth + rightWidth + testWidth > Utilities.getContentWidth(that.element);
};
var clearTestStack = function () {
Utilities.empty(that._testStack);
testWidth = 0;
};
var deleteOverflowElements = function (priorityGroup) {
priorityGroup.forEach(function (mapping) {
that._removeFromOverflowList(mapping.command.id);
that._removeCommand(mapping.command.id);
});
};
// See if there are any overflowed commands we can bring back
if (count > 0) {
var keepGoing = true;
for (var i = 0, ilen = that._commandMap.indexes.length; i < ilen; i++) {
var priority = that._commandMap.indexes[i];
if (that._overflowedCommands.indexOf(that._commandMap[priority][0].command.id) > -1) {
var commandElements = [];
// This priority group is overflowed, see if it will fit on the horizontal bar
for (var j = 0, jlen = that._commandMap[priority].length; j < jlen; j++) {
var mapping = that._commandMap[priority][j];
var state = Number(that._findCommandElement(mapping.command.id).getAttribute("state"));
var element = that._renderCommand(mapping.command, state, classSuffixHoriz);
if (willElementOverflow(element, mapping.group.showSeparator)) {
keepGoing = false;
break;
} else {
// Put the element and its CommandGroup in an object, push into array
commandElements.push({ group: mapping.group, command: element });
}
}
clearTestStack();
if (!keepGoing) {
break;
} else {
// The last priority group can be de-overflowed
deleteOverflowElements(that._commandMap[priority]);
commandElements.forEach(function (commandElement) {
// Get the parent group, render and insert the command
var groupElement = that._renderCommandGroup(commandElement.group, null);
that._insertByIndex(groupElement, commandElement.command);
});
}
}
}
}
var findLowerPriCommands = function (priority) {
for (var i = that._commandMap.indexes.length - 1; i >= 0; i--) {
var index = that._commandMap.indexes[i];
if (index > priority) {
var commands = getCommandSetByPriority(index, true);
if (commands.length > 0) {
return commands;
}
} else {
break;
}
}
return null;
};
var getCommandSetByPriority = function (priority, renderedOnly) {
var commands = [];
that._commandMap[priority].forEach(function (mapping) {
if (!renderedOnly || that._findCommandElement(mapping.command.id)) {
commands.push({ group: mapping.group, command: mapping.command });
}
});
return commands;
};
var renderGroupCommandObject = function (command, group, targetStack) {
// Render the command using the vertical template
var commandElement = that._renderCommand(command, command.initialState, classSuffixVert);
if (targetStack !== group.commandStack) {
commandElement.setAttribute("isOverflow", true);
}
return { group: group, command: commandElement };
};
var overflow = false;
// Render the commands in priority order, and overflow to the expansion menu if needed
that._commandMap.indexes.forEach(function (priority) {
that._commandMap[priority].forEach(function (mapping) {
// If the command doesn't currently exist...
if (!that._findCommandElement(mapping.command.id)) {
var targetStack = mapping.group.commandStack;
var elements = [];
// And we're not overflowed yet, and it lives in one of the horizontal stacks...
if (!overflow && targetStack !== WinUI.CommandStack.expansion) {
// Render the command
var currentSet = { group: mapping.group, command: that._renderCommand(mapping.command, mapping.command.initialState, classSuffixHoriz) };
// See if we will overflow the available space
while (willElementOverflow(currentSet.command, mapping.group.showSeparator)) {
// Overflow. See if there are any lower-priority commands to migrate
var commandSets = findLowerPriCommands(priority);
if (!commandSets) {
// None found, so overflow everything from here forward
commandSets = getCommandSetByPriority(priority, false);
targetStack = WinUI.CommandStack.expansion;
overflow = true;
}
commandSets.forEach(function (set) {
that._removeCommand(set.command.id);
that._overflowedCommands.push(set.command.id);
elements.push(renderGroupCommandObject(set.command, set.group, targetStack));
});
if (overflow) {
break;
}
}
if (elements.length === 0) {
elements.push(currentSet);
}
} else {
targetStack = WinUI.CommandStack.expansion;
// TODO: get the current state and render in that state
elements.push(renderGroupCommandObject(mapping.command, mapping.group, targetStack));
}
elements.forEach(function (element) {
// Get the parent group, then render and insert the command
var groupElement = that._renderCommandGroup(element.group, targetStack);
that._insertByIndex(groupElement, element.command);
});
clearTestStack();
}
});
});
});
},
// Renders the command bar vertically
_renderVertical: function CommandBar_renderVertical(commandNames) {
var count = 0;
var stack = WinUI.CommandStack.expansion;
var that = this;
return this._removeHiddenCommands(commandNames)
.then(function () {
that._commandStacks[stack].groups.forEach(function (group) {
var elements = [];
group.commands.forEach(function (command) {
if (commandNames.indexOf(command.id) > -1) {
var id = formatString(commandElementId, [that._uniqueId, command.id]);
var element = document.getElementById(id);
if (!element) {
element = that._renderCommand(command, command.initialState, classSuffixVert);
element.setAttribute("index", command.index);
elements.push(element);
}
}
});
if (elements.length > 0) {
var groupElement = that._renderCommandGroup(group, WinUI.CommandStack.expansion);
elements.forEach(function (commandElement) {
that._insertByIndex(groupElement, commandElement);
});
}
});
});
},
_removeHiddenCommands: function CommandBar_removeHiddenCommands(commandSet) {
var count = 0;
var that = this;
var promises = [];
// Look for commands to hide
this._currentCommandSet.forEach(function (commandName) {
if (commandSet.indexOf(commandName) === -1) {
promises.push(that._removeCommand(commandName)
.then(function (overflowCount) {
count += overflowCount;
if (that._overflowedCommands) {
that._removeFromOverflowList(commandName);
}
})
);
}
});
if (promises.length > 0) {
return Promise.join(promises)
.then(function () {
return Promise.wrap(count);
});
} else {
return Promise.wrap(0);
}
},
_removeFromOverflowList: function CommandBar_removeFromOverflowList(commandName) {
var index = this._overflowedCommands.indexOf(commandName);
if (index > -1) {
this._overflowedCommands.splice(index, 1);
}
},
// Inserts the specified element in the parent, based upon the element's index attribute and those of its peers
_insertByIndex: function CommandBar_insertByIndex(parent, element) {
var index = Number(element.getAttribute("index"));
var isGroup = element.hasAttribute("stack");
for (var i = 0, len = parent.children.length; i < len; i++) {
var node = parent.children[i];
var dex = Number(node.getAttribute("index"));
if (isNaN(dex) || dex > index) {
if (isGroup) {
this._checkAndRenderSeparator(element);
}
node.insertAdjacentElement("beforeBegin", element);
return;
}
}
if (isGroup && parent.lastElementChild) {
this._checkAndRenderSeparator(parent.lastElementChild);
}
parent.appendChild(element);
},
// Renders the specified Command
_renderCommand: function CommandBar_renderCommand(command, stateIndex, classSuffix) {
var element = null;
if (command.hostedElement) {
element = document.createElement("div");
element.id = formatString(commandElementId, [this._uniqueId, command.id]);
element.setAttribute("hostedElement", "true");
Utilities.addClass(element, classes["commandHosted" + classSuffix]);
element.appendChild(command.hostedElement);
} else {
element = this["commandTemplate" + classSuffix].cloneNode(true);
var data = command.states[stateIndex].templateValues;
var that = this;
var replaceTokens = function (element) {
if (element.hasChildNodes()) {
for (var c = 0, clen = element.childNodes.length; c < clen; c++) {
replaceTokens(element.childNodes[c]);
}
}
function insertValue(str, field) {
var value = data[field];
return value !== undefined ? value : str;
};
var property = null;
if (element.attributes) {
for (var a = 0, alen = element.attributes.length; a < alen; a++) {
var value = element.attributes[a].nodeValue;
element.setAttribute(element.attributes[a].nodeName, value.replace(tokenPattern, insertValue));
}
} else if (element.textContent) {
element.textContent = element.textContent.replace(tokenPattern, insertValue);
}
};
replaceTokens(element);
Utilities.addClass(element, classes["command" + classSuffix]);
if (classSuffix === classSuffixHoriz) {
this._setCommandImageEventState(element, "rest");
element.addEventListener("mouseenter", function () {
that._setCommandImageEventState(element, "hover");
}, false);
element.addEventListener("mousedown", function () {
that._setCommandImageEventState(element, "press");
}, false);
element.addEventListener("MSPointerDown", function () {
that._setCommandImageEventState(element, "press");
}, false);
element.addEventListener("mouseup", function () {
that._setCommandImageEventState(element, "hover");
}, false);
element.addEventListener("MSPointerUp", function () {
that._setCommandImageEventState(element, "rest");
}, false);
element.addEventListener("mouseleave", function () {
that._setCommandImageEventState(element, "rest");
}, false);
}
if (command.isExpansion) {
element.setAttribute("isExpando", true);
}
element.msDataItem = command;
element.setAttribute("state", stateIndex);
element.addEventListener("click", function (event) {
that._commandInvoked(event);
}, false);
}
if (command.tooltip) {
var placement;
if (classSuffix === classSuffixHoriz) {
if (this._parentAppBar) {
placement = this._parentAppBar.position === "bottom" ? "top" : "bottom";
} else {
placement = "top";
}
} else {
placement = "left";
}
var toolTip = new WinUI.Tooltip(element, {
placement: placement,
delay: true
});
toolTip.addEventListener("beforeopen", function () {
var content = that._tooltipTemplate.cloneNode(true);
data = {};
data.title = command.tooltip.title || command.states[stateIndex].templateValues.commandName;
data.shortcut = command.tooltip.shortcutKey ? formatString(that._tooltipShortcutFormat, [command.tooltip.shortcutKey]) : "";
data.description = command.tooltip.description;
replaceTokens(content);
toolTip.contentElement = content;
}, false);
}
element.setAttribute("index", command.index);
element.addEventListener("focus", function (event) {
that._commandFocused(event);
}, false);
return element;
},
// Sets the specified command's image based on the specified state (rest, hover, press)
_setCommandImageEventState: function CommandBar_setCommandImageEventState(element, state) {
var image = element.getElementsByClassName("commandIcon")[0];
image.src = image.getAttribute("data-win-template").replace("{{state}}", state);
},
// Removes the command element with the specified name
_removeCommand: function CommandBar_removeCommand(commandName) {
var that = this;
return new Promise(function (complete) {
var element = that._findCommandElement(commandName);
var isOverflow = false;
if (element) {
isOverflow = element.hasAttribute("overflow");
if (element.hasAttribute("hostedElement")) {
that._customElementsHost.appendChild(element.firstElementChild);
}
var parent = element.parentNode;
Promise.timeout()
.then(function () {
element.removeNode(true);
if (parent) {
that._checkAndRemoveEmptyGroup(parent);
}
complete(isOverflow ? 0 : 1);
});
}
});
},
// Locates the Command object with the specified name
_findCommand: function CommandBar_findCommand(commandName) {
this._commandGroups.forEach(function (group) {
group.commands.forEach(function (command) {
if (command.id === commandName) {
return command;
}
});
});
return null;
},
// Locates the command element with the specified name
_findCommandElement: function CommandBar_findCommandElement(commandName) {
var id = formatString(commandElementId, [this._uniqueId, commandName]);
return document.getElementById(id);
},
// Renders the specified command group
_renderCommandGroup: function CommandBar_renderCommandGroup(commandGroup, commandStack) {
var id = commandGroup.id;
if (commandStack && commandStack !== commandGroup.commandStack) {
// This group is being overflowed, give it a special id
id += "_exp";
}
var groupHost = this._findCommandGroupElement(id);
var classSuffix = commandStack === WinUI.CommandStack.expansion ? classSuffixVert : classSuffixHoriz;
if (!groupHost) {
groupHost = document.createElement("div");
groupHost.id = formatString(groupElementId, [this._uniqueId, id]);
groupHost.role = commandStack === WinUI.CommandStack.expansion ? "list" : "menu";
groupHost.setAttribute("aria-label", commandGroup.name);
groupHost.setAttribute("aria-describedby", formatString(commandDescribedById, [this._uniqueId, commandGroup.id]));
groupHost.setAttribute("stack", commandStack);
groupHost.setAttribute("index", commandGroup.index);
groupHost.setAttribute("showSeparator", commandGroup.showSeparator);
Utilities.addClass(groupHost, classes["commandGroup" + classSuffix]);
if (!commandStack) {
commandStack = commandGroup.commandStack;
}
var parentStack = this._commandStacks[commandStack].element;
this._insertByIndex(parentStack, groupHost);
}
return groupHost;
},
// Locates the group element with the specified name
_findCommandGroupElement: function CommandBar_findCommandGroupElement(groupName) {
var id = formatString(groupElementId, [this._uniqueId, groupName]);
return document.getElementById(id);
},
// Checks the specified group for commands, and removes it if it contains none
_checkAndRemoveEmptyGroup: function CommandBar_checkAndRemoveEmptyGroup(groupElement) {
var removeGroup = function () {
// TODO: add animation, return promise
groupElement.removeNode(true);
return Promise.wrap();
};
if (groupElement.children.length === 0) {
var stackElement = groupElement.parentNode;
removeGroup()
.then(function () {
// If the parent stack's last group has a separator, remove it
if (stackElement && stackElement.lastElementChild && stackElement.lastElementChild.lastElementChild) {
if (stackElement.lastElementChild.lastElementChild.hasAttribute("separator")) {
stackElement.lastElementChild.lastElementChild.removeNode(true);
}
}
});
} else if (groupElement.children[0].hasAttribute("separator")) {
removeGroup();
}
},
// Checks the specified group for a separator and adds one if needed
_checkAndRenderSeparator: function CommandBar_checkAndRenderSeparator(groupElement) {
if (groupElement) {
var show = Boolean(groupElement.getAttribute("showSeparator"));
if (show && (!groupElement.hasChildNodes() || !groupElement.lastElementChild.hasAttribute("separator"))) {
this._renderSeparator(groupElement);
}
}
},
// Renders a separator in the specified group
_renderSeparator: function CommandBar_renderSeparator(groupElement) {
var stack = groupElement.getAttribute("stack");
var classSuffix = stack === WinUI.CommandStack.expansion ? classSuffixVert : classSuffixHoriz;
var sep = document.createElement("div");
sep.setAttribute("separator", "separator");
Utilities.addClass(sep, classes["separator" + classSuffix]);
if (stack !== WinUI.CommandStack.left) {
sep.setAttribute("index", "999");
}
groupElement.appendChild(sep);
},
// Builds a mapping between commands and groups, ranked by command priority
_buildCommandMap: function CommandBar_createCommandMap(commandNames) {
var map = {};
map.indexes = [];
map.groupIndex = [];
var seen = [];
var that = this;
Object.keys(this._commandStacks).forEach(function (stack) {
that._commandStacks[stack].groups.forEach(function (group) {
group.commands.forEach(function (command) {
var index = commandNames.indexOf(command.id);
if (index > -1) {
if (!map[command.priority]) {
map[command.priority] = [];
}
map[command.priority].push({ command: command, group: group, index: group.commands.indexOf(command) });
if (map.indexes.indexOf(command.priority) === -1) {
// Add the priority value to the list, so we can sort them numerically
map.indexes.push(command.priority);
}
if (map.groupIndex.indexOf(group.id) === -1) {
// Add the group ID to the index
map.groupIndex.push(group.id);
}
seen.push(index);
}
});
});
});
for (var i = commandNames.length - 1; i >= 0; i--) {
if (seen.indexOf(i) === -1) {
commandNames.splice(i, 1);
}
}
map.indexes.sort(function (a, b) { return a - b; });
return map;
},
// Set an attribute on the AppBar to make it transparent if we aren't showing any commands
_setAppBarTransparency: function CommandBar_setAppBarTransparency() {
if (this._parentAppBarElement) {
this._parentAppBarElement.setAttribute("transparent", this._currentCommandSet.length === 0);
}
}
}),
CommandGroup: WinNS.Class.define(function (id, stack, index) {
/// <summary>
/// Creates an instance of the CommandGroup class
/// </summary>
/// <param name='id'>
/// A unique string identifier
/// </param>
/// <param name='stack'>
/// WinJS.UI.CommandStack indicating the parent stack for this group
/// </param>
/// <param name='index'>
/// The index position of this group in the stack
/// </param>
/// <returns type="WinJS.UI.CommandGroup" />
if (!id) {
throw new Error(missingParameter + "id");
}
this._id = id;
this._name = "";
this._description = "";
this._commands = [];
this._commandStack = stack || WinUI.CommandStack.right;
this._index = index || 0;
this._showSeparator = true;
this._templateFields = ["id", "accessKey"];
},
{
id: {
/// <summary>
/// Gets the unique identifier of this command group
/// </summary>
get: function () {
return this._id;
}
},
name: {
/// <summary>
/// Gets or sets the string name of this command group, used for accessibility
/// </summary>
get: function () {
return this._name;
},
set: function (value) {
if (typeof value === "string") {
this._name = value;
}
}
},
description: {
/// <summary>
/// Gets or sets the string description of this command, used for accessibility
/// </summary>
get: function () {
return this._description;
},
set: function (value) {
if (typeof value === "string") {
this._description = value;
}
}
},
commands: {
/// <summary>
/// Gets the array of WinJS.UI.Command objects contained in this group
/// </summary>
get: function () {
return this._commands;
}
},
commandStack: {
/// <summary>
/// Gets the command stack to which this group belongs
/// </summary>
get: function () {
return this._commandStack;
}
},
index: {
/// <summary>
/// Gets or sets an integer value indicating the display order of this group. This value must be zero or larger
/// </summary>
get: function () {
return this._index;
},
set: function (value) {
if (typeof value === "number" && value >= 0) {
this._index = value;
}
}
},
showSeparator: {
/// <summary>
/// Gets or sets a Boolean value indicating whether a separator should be shown following this group
/// </summary>
get: function () {
return this._showSeparator;
},
set: function (value) {
if (typeof value === "boolean") {
this._showSeparator = value;
}
}
},
// Appends the specified command to this group
addCommand: function CommandGroup_addCommand(command) {
/// <summary>
/// Appends the specified command to this group
/// </summary>
/// <param name='command'>
/// The WinJS.UI.Command object to be appended
/// </param>
/// <returns>
/// None
/// </returns>
for (var i = 0, ilen = command.states.length; i < ilen; i++) {
for (var j = 0, jlen = this._templateFields.length; j < jlen; j++) {
if (!command.states[i].templateValues[this._templateFields[j]]) {
command.states[i].templateValues[this._templateFields[j]] = command[this._templateFields[j]];
}
}
}
command.index = this._commands.length;
this._commands.push(command);
}
}),
Command: WinNS.Class.define(function (id, states, priority) {
/// <summary>
/// Creates an instance of the Command class
/// </summary>
/// <param name='id'>
/// A unique string identifier
/// </param>
/// <param name='states'>
/// An array of object literals that describe one or more states of the command
/// </param>
/// <param name='priority'>
/// A numeric value that dictates the order in which this command will be moved to the expansion stack, as needed when space is constrained
/// </param>
/// <returns type="WinJS.UI.Command" />
if (!id) {
throw new Error(missingParameter + "id");
}
this._id = id;
this._description = "";
this._shortcutKey = "";
this._states = states || [];
this._initialState = 0;
this._isExpansion = false;
this._priority = 1;
this._previousPriority = this._priority;
this._hostedElement = null;
this._accessKey = "";
this._hideOnInvoke = true;
this._advanceState = true;
this.priority = priority;
},
{
id: {
/// <summary>
/// Gets the unique identifier of this command
/// </summary>
get: function () {
return this._id;
}
},
description: {
/// <summary>
/// Gets or sets the string description of this command, used for accessibility
/// </summary>
get: function () {
return this._description;
},
set: function (value) {
if (typeof value === "string") {
this._description = value;
}
}
},
tooltip: {
/// <summary>
/// Gets or sets the CommandTooltip to be displayed for this command
/// </summary>
get: function () {
return this._tooltip;
},
set: function (value) {
if (value instanceof WinUI.CommandTooltip) {
this._tooltip = value;
}
}
},
states: {
/// <summary>
/// Gets the array of state objects for this command
/// </summary>
get: function () {
return this._states;
}
},
initialState: {
/// <summary>
/// Gets or sets the index of the initial state for this command
/// </summary>
get: function () {
return this._initialState;
},
set: function (value) {
if (typeof value === "number") {
this._initialState = value;
}
}
},
isExpansion: {
/// <summary>
/// Gets or sets a value indicating whether this command raises the expansion flyout
/// </summary>
get: function () {
return this._isExpansion;
},
set: function (value) {
if (typeof value === "boolean") {
this._isExpansion = value;
if (this._isExpansion) {
this._priority = 0;
} else {
this._priority = this._previousPriority;
}
}
}
},
priority: {
/// <summary>
/// Gets or sets the numeric priority value of this command. Value must be greater than zero.
/// </summary>
get: function () {
return this._priority;
},
set: function (value) {
if (!this.isExpansion && typeof value === "number" && value > 0) {
this._priority = this._previousPriority = value;
}
}
},
accessKey: {
/// <summary>
/// Gets or sets the access key for this command
/// </summary>
get: function () {
return this._accessKey;
},
set: function (value) {
this._accessKey = value;
}
},
hideOnInvoke: {
/// <summary>
/// Gets or sets a value indicating whether invoking this command causes the command bar to hide
/// </summary>
get: function () {
return this._hideOnInvoke;
},
set: function (value) {
if (typeof value === "boolean") {
this._hideOnInvoke = value;
}
}
},
advanceState: {
/// <summary>
/// Gets or sets a value indicating whether invoking this command automatically moves to its next state. This property has no effect on single-state commands
/// </summary>
get: function () {
return this._advanceState;
},
set: function (value) {
if (typeof value === "boolean") {
this._advanceState = value;
}
}
},
hostedElement: {
/// <summary>
/// Gets or sets an external DOM element to be hosted by this command
/// </summary>
get: function () {
return this._hostedElement;
},
set: function (value) {
if (value.tagName) {
this._hostedElement = value;
}
}
}
}),
CommandTooltip: WinNS.Class.define(function (title, shortcutKey, description) {
/// <summary>
/// Creates an instance of the CommandTooltip class
/// </summary>
/// <param name='title'>
/// The title that will be shown on the tooltip. Provide null or empty string to use the command's label.
/// </param>
/// <param name='shortcutKey'>
/// A string describing the shortcut key or chord that invokes the command. Provide null or empty string if no shortcut key is supported.
/// </param>
/// <param name='description'>
/// The body text to be displayed on the tooltip
/// </param>
/// <returns type="WinJS.UI.CommandTooltip" />
this._title = title;
this._shortcutKey = shortcutKey;
this._description = description;
},
{
title: {
/// <summary>
/// Gets or sets the command title
/// </summary>
get: function () {
return this._title;
},
set: function (value) {
if (typeof value === "string") {
this._title = value;
}
}
},
shortcutKey: {
/// <summary>
/// Gets or sets the shortcut key, as defined by the application (e.g., "Ctrl+Shift+K")
/// </summary>
get: function () {
return this._shortcutKey;
},
set: function (value) {
if (typeof value === "string") {
this._shortcutKey = value;
}
}
},
description: {
/// <summary>
/// Gets or sets the command description
/// </summary>
get: function () {
return this._description;
},
set: function (value) {
if (typeof value === "string") {
this._description = value;
}
}
}
})
});
})(WinJS);