- Controls
Controls are the building blocks of your experience. They allow users to view, edit, and analyze data.
To learn more about any of our controls, click on any of the links below.
- Button
- Copyable Label
- File Upload (async)
- File Download Button
- Text Block
- OAuth Button
- Splitter
- Selector
- Settings
- Single Setting
- Toolbar
- Markdown Control
- Date Picker
- Date/Time Picker
- Date/Time Range Picker
- Date Polyfills
- Day Picker
- Duration Picker
- Time Picker
- Drop Down
The AMD dropdown has all the features of the old dropdowns. You can now turn on filtering or add grouping to a multiselect dropdown where previously adding a featuring might mean porting your code to a different control (or wasn't possible depending on combination of features you were looking for).
The AMD dropdown supports:
- Grouping
- Rich Templating
- Filtering
- Custom Filtering
- Multiselect
- Objects as the value
You can use it by importing the AMD module:
import * as DropDown from "Fx/Controls/DropDown";
Creating the view model:
// Items to popuplate the dropdown with.
let myItems = [{
text: "Sample text",
value: "SampleValue"
}, {
text: "Sample text 2",
value: "SampleValue2"
}];
this.dropDownVM = DropDown.create(container, {
items: myItems
});
Grouping is simple by expanding the dropdown item with a group type.
let myItems = [{
text: "Sample header text",
children: [{
text: "Sample text",
value: "SampleValue"
}, {
text: "Sample text 2",
value: "SampleValue2"
}]
}]
You are able to group multiple groups together to create a nested layout.
For large list of items you are able to turn on filtering: true
to enable searching.
this.dropDownVM = DropDown.create(container, {
items: myItems,
filter: true
});
Popuplates the search both with a placeholder, overwrites the placeholder
property on the dropdown..
this.dropDownVM = DropDown.create(container, {
items: myItems,
filter: true,
filterPlaceholder: "Search items"
});
When you need multiple items selected we support multiselect: true
to allow this. We will then show items selected as "X selected". The multiselect dropdown doesn't use placeholder
, use below multiItemDisplayText
.
this.dropDownVM = DropDown.create(container, {
items: myItems,
multiselect: true
});
If you want to change the format of the default text, you may set multiItemDisplayText
. Include a {0} in the replaced string if you want to include the number of items selected.
this.dropDownVM = DropDown.create(container, {
items: myItems,
multiselect: true,
multiItemDisplayText: "{0} subscriptions"
});
The dropdown supports both filtering & multiselect states to be active. The filter textbox will move into the dropdown.
this.dropDownVM = DropDown.create(container, {
items: myItems,
filter: true,
multiselect: true
});
The dropdown supports both filtering & multiselect states to be active. The filter textbox will move into the dropdown.
let myItems = [{
text: "<b>G1</b> - large"
value: "large"
},{
text: "<b>G2</b> - small"
value: "small"
}];
this.dropDownVM = DropDown.create(container, {
items: myItems
});
Adds a default string to show if nothing is selected.'
this.dropDownVM = DropDown.create(container, {
items: myItems,
placeholder: "Select an item"
});
This a readonly observable which you can subscribe to know when the dropdown is opened/closed. Useful for delay loading your items they are popuplated from an expensive ajax call.
this.dropDownVM.isPopUpOpen.subscribe(container, (opened) => {
if (opened) {
// make your expensive call here.
}
});
We handle most accessibility, one important note though is if you use an html template or image binding in your item text. You need to add an ariaLabel on that item.
Check out the documentation here: https://github.com/Azure/portaldocs/blob/dev/portal-sdk/templates/portalfx-controls-dropdown-migration.md
- Code editor
Editor control in the FX SDK is a wrapper for the "Monaco" editor which supports various languages, syntax highligting, intellisense, real-time syntax checking and validation.
To use the editor, compose a part that hosts the editor control, then use it from your extension.
You can control the behavior and features of the editor via initialization options
.
Step 1: Define the Html template for your part:
\Client\V1\Controls\Editor\Templates\EditorInstructions.html
Step 2: Create a viewmodel to bind your control to. SampleEditorViewModel implements the viewmodel for the editor.
\Client\V1\Controls\Editor\ViewModels\EditorViewModels.ts.
/**
* ViewModel class for the editor sample.
*/
export class SampleEditorViewModel extends MsPortalFx.ViewModels.Controls.Documents.Editor.ViewModel {
/**
* Editor view model constructor.
*/
constructor(lifetimeManager: MsPortalFx.Base.LifetimeManager) {
// Mock up sample javascript file content.
const content =
[
"function test1(name, job) {",
" alert('Welcome ' + name + ', the ' + job);",
"}",
"",
"function test2() {",
" var x = '', i = 0;",
" for (i = 0; i < 10; i++) {",
" if (i == 3) {",
" continue;",
" }",
" x = x + 'The number is ' + i;",
" }",
" document.getElementById('demo').innerHTML = x;",
"}"
].join("\n");
// Set up whether or not to show line numbers and what the tab size is in the editor.
const options = {
lineNumbers: false,
enhancedScrollbar: true,
tabSize: 4,
wrappingColumn: 0,
};
// Initialize the editor with the above content and options, as well as set the type to be JavaScript.
super(lifetimeManager, MsPortalFx.ViewModels.Controls.Documents.Editor.ContentType.JavaScript, content, options);
}
}
/**
* ViewModel class for the editor sample part.
*/
export class EditorInstructionsPartViewModel
implements Def.EditorInstructionsPartViewModel.Contract {
/**
* View model for the editor.
*/
public editorVM: MsPortalFx.ViewModels.Controls.Documents.Editor.Contract;
/**
* View model for the save button.
*/
public saveButton: Button.Contract;
/**
* Creates a new instance of the EditorInstructionsPartViewModel class.
*
* @param container The view model for the part container.
* @param initialState The initial state for the part.
* @param dataContext The data context.
*/
constructor(
container: MsPortalFx.ViewModels.PartContainerContract,
initialState: any,
dataContext: ControlsArea.DataContext) {
// Initialize the editor view model. If we were getting the data from teh data context, we would pass it in here.
this.editorVM = new SampleEditorViewModel(container);
// Initialize the save button and wire it up such that it saves the content of the editor.
this.saveButton = Button.create(container, {
text: ClientResources.Editor.save,
onClick: () => {
this.editorVM.save.execute().then(() => {
// Here is where you would put code that is executed after any changes have been written back to the content property on the viewmodel.
});
}
});
}
}
Step 3: Now you can consume your part from an extension by referencing it in the PDL:
\Client\V1\Controls\Editor\Editor.pdl
<CustomPart Name="b_EditorInstructions_part1"
ViewModel="{ViewModel Name=EditorInstructionsPartViewModel, Module=./Editor/ViewModels/EditorViewModels}"
InitialSize="FullWidthFitHeight"
Template="{Html Source='Templates\\EditorInstructions.html'}" />
<PartReference Name="EditorApiReferencePart"
InitialSize="FullWidthFitHeight"
PartType="ModuleReferencePart">
<PartReference.PropertyBindings>
<Binding Property="moduleName"
Source="{Constant MsPortalFx.ViewModels.Controls.Documents.Editor}" />
</PartReference.PropertyBindings>
</PartReference>
Custom language can be used by declaring inherited Editor control viewmodel with rules and suggestions.
Step 1: Define the Html template for your part:
\Client\V1\Controls\Editor\Templates\CustomLanguageEditor.html
Step 2: Create a viewmodel to bind your control to. SampleEditorViewModel implements the viewmodel for the editor.
\Client\V1\Controls\Editor\ViewModels\CustomLanguageEditorViewModels.ts.
/**
-
ViewModel class for the custom language editor sample part. */ export class CustomLanguageEditorPartViewModel {
/**
- View model for the editor. */ public editor: Editor.ViewModel;
/**
-
Creates a new instance of the EditorInstructionsPartViewModel class.
-
@param container The view model for the part container.
-
@param initialState The initial state for the part.
-
@param dataContext The data context. */ constructor( container: MsPortalFx.ViewModels.PartContainerContract, initialState: any, dataContext: ControlsArea.DataContext) {
// Initialize the editor view model. If we were getting the data from teh data context, we would pass it in here. this.editor = new CustomLanguageEditorViewModel(container);
// create the initial markers this.updateMarkers(this.editor.content());
// whenever the code in the editor changes process it and set the markers this.editor.content.subscribe(container, this.updateMarkers.bind(this));
// perform auto save every 500ms to update markers as the user edits the text this.editor.autoSaveTimeout(500); }
/**
-
try to match the given pattern in the provided line, if matched construct a marker for that line */ private processTerm(line: string, lineIndex: number, termPattern: string, severity: MsPortalFx.ViewModels.Controls.Documents.Editor.MarkerSeverity): MsPortalFx.ViewModels.Controls.Documents.Editor.EditorMarker {
// try to match the pattern var termIndex = line.indexOf(termPattern); if (termIndex === -1) { return null; }
// we found the pattern in the line so we create a marker which uses the defined severity and the rest of the text // from the pattern location in the line to the end as the message return <MsPortalFx.ViewModels.Controls.Documents.Editor.EditorMarker> { message: line.substring(termIndex + termPattern.length), severity: severity, range: <MsPortalFx.ViewModels.Controls.Documents.Editor.EditorRange> { startLineNumber: lineIndex + 1, startColumn: termIndex + 1, endLineNumber: lineIndex + 1, endColumn: termIndex + termPattern.length + 1 } }; }
/**
-
updates the markers according to the text content */ private updateMarkers(value: string): void {
// split the text into lines and process each to add an annotation to it var markers = value .split("\n") .map((line: string, index: number): MsPortalFx.ViewModels.Controls.Documents.Editor.EditorMarker => {
var term = this.processTerm(line, index, "[error]", MsPortalFx.ViewModels.Controls.Documents.Editor.MarkerSeverity.Error); if (term !== null) { return term; } term = this.processTerm(line, index, "[notice]", MsPortalFx.ViewModels.Controls.Documents.Editor.MarkerSeverity.Warning); if (term !== null) { return term; } term = this.processTerm(line, index, "[info]", MsPortalFx.ViewModels.Controls.Documents.Editor.MarkerSeverity.Info); if (term !== null) { return term; } return null; }) .filter(marker => marker !== null);
// update the markers this.editor.markers(markers); } }
Step 3: Now you can consume your part from an extension by referencing it in the PDL:
\Client\V1\Controls\Editor\Editor.pdl
<CustomPart Name="b_EditorInstructions_part1"
ViewModel="{ViewModel Name=EditorInstructionsPartViewModel, Module=./Editor/ViewModels/EditorViewModels}"
InitialSize="FullWidthFitHeight"
Template="{Html Source='Templates\\EditorInstructions.html'}" />
<PartReference Name="EditorApiReferencePart"
InitialSize="FullWidthFitHeight"
PartType="ModuleReferencePart">
<PartReference.PropertyBindings>
<Binding Property="moduleName"
Source="{Constant MsPortalFx.ViewModels.Controls.Documents.Editor}" />
</PartReference.PropertyBindings>
</PartReference>
- Diff editor
- Console
The console control provides a REPL like experience which can be used to replicate a Bash/PowerShell/Batch like experience.
\SamplesExtension\Extension\Client\Controls\Console\Templates\ConsoleInstructions.html
<div data-bind='pcConsole: consoleViewModel'></div>
\SamplesExtension\Extension\Client\Controls\Console\ViewModels\ConsoleViewModels.ts
public consoleViewModel: MsPortalFx.ViewModels.Controls.Console.Contract;
constructor(container: MsPortalFx.ViewModels.PartContainerContract,
initialState: any,
dataContext: ControlsArea.DataContext) {
// Initialize the console view model.
this.consoleViewModel = new MsPortalFx.ViewModels.Controls.Console.ViewModel();
// To get input from the user, subscribe to the command observable.
// This is where you would parse the incomming command.
// To show output, you can set the info, warning, success and error properties.
this.consoleViewModel.command.subscribe(container, (s) => {
// In this example, we are just piping back the input as an info, warning, success or error message based off of what you type in.
switch (s) {
case ClientResources.consoleInfo:
this.consoleViewModel.info(s);
break;
case ClientResources.consoleWarning:
this.consoleViewModel.warning(s);
break;
case ClientResources.consoleSuccess:
this.consoleViewModel.success(s);
break;
case ClientResources.consoleError:
this.consoleViewModel.error(s);
break;
default:
this.consoleViewModel.error(s);
}
});
// You can also observably set the prompt which is displayed above the > in the console.
this.consoleViewModel.prompt = ko.observable(ClientResources.consolePrompt);
}
-
Check Box
-
Text Boxes
-
Sliders
- Chart
Insert chart controls in your experience to allow your users to visualize and analyze their data.
In most cases, you will probably want to use the chart intrinsic part. The intrinsic part is maintained by the framework and will provide you with consistent layout with the rest of the portal.
If you are using a custom part template, charts can be added with the following html:
<div data-bind='pcChart: chartVM' style='height:500px'></div>
Our charts include the following chart view types which can be used separately or in tandem:
- Line
- Grouped bar
- Stacked bar
- Scatter
- Area
- Stacked area
Chart views are the high-level view type for your chart.
// Initialize the view. This is the code that makes this chart a bar chart.
var barChartView = new MsPortalFx.ViewModels.Controls.Visualization.Chart.BarChartView<string, number>(MsPortalFx.ViewModels.Controls.Visualization.Chart.BarChartType.Grouped);
this.chartVM.views([barChartView]);
A sample chart viewmodel with a single chart view type can be found here:
\Client\Controls\Chart\ViewModels\BarChartViewModels.ts
A sample chart viewmodel with multiple chart view types can be found here:
\Client\Controls\Chart\ViewModels\OverlayedViewChartViewModel.ts
Series views are visualizations of individual data series. Series views allow you to modify the color, display name, and interaction behavior of a particular series.
By default, series views will be generated for each of the chart views and each of the data series you add to your chart. For example, let's say you added three data series, seriesA, seriesB, and seriesC to a chart that has two chart views, a bar chart view and a line chart view. Your chart would have 6 series views, a bar chart series view and a line chart series view for each series. This default behavior is ideal for simple charts, especially those with one chart view type.
In some cases you may want to do some more interesting things with series views. Perhaps instead you want seriesA and seriesB to be visualized as bars and seriesC to be visualized as a line. To achieve this behavior you will need to turn off the auto-generate behavior.
this.chartVM.autogenerateSeriesViews(false);
You can then create and specialize your series views however you'd like.
var lineSeriesView = new MsPortalFx.ViewModels.Controls.Visualization.Chart.LineChartSeriesView<string, number>();
lineSeriesView.seriesName("LineSeries");
lineSeriesView.cssClass("msportalfx-bgcolor-c1");
var barSeriesView = new MsPortalFx.ViewModels.Controls.Visualization.Chart.SeriesView<string, number>(MsPortalFx.ViewModels.Controls.Visualization.Chart.BarChartType.Stacked);
barSeriesView.seriesName("BarSeries");
lineChartView.seriesView([lineSeriesView]);
barChartView.seriesView([barSeriesView]);
A good example of using the chart's auto-generated series views functionality is:
\Client\Controls\Chart\ViewModels\LineChartDateBasedViewModels.ts
To see a more advanced sample where series views are created explicitly by the extension see
\Client\Controls\Chart\ViewModels\OverlayedViewChartViewModels.ts
Metrics are the big data call-outs that pair with our chart controls to give the user interactive peeks into their data. Metrics can be configured manually by handling chart events, calculating values, and passing information to the metrics controls or by setting up metrics rules.
Metrics rules are a rule-based system that automatically hooks up metric values to different user interactions. For instance, by default (when the user is not interacting with the chart area) you may want your chart metrics to show the average value of each data series. This rule can be configured like so:
metricRule1.scope(Chart.MetricRuleScope.Default);
metricRule1_metric1.aggregationScope(Chart.MetricRuleAggregationScope.AllSeparately);
metricRule1_metric1.aggregationType(Chart.MetricRuleAggregationType.AverageY);
With this rule configured, when the user is not interacting with the chart area they will see one metric representing the average value of each data series on the chart.
See the following file for a full example of the metrics rules implementation:
\Client\Controls\Chart\ViewModels\LineChartDateTimeViewModels.ts
- Donut
Donut charts are a great way to visualize proportional data.
Donuts can be added to your part templates with the following html:
<div data-bind='pcDonut: donutVM' style='height:500px; width:500px'></div>
A sample view model can be found here:
\Client\Controls\Donut\ViewModels\DonutViewModels.ts
Other visualization controls: