added ui styling & added item creation component

This commit is contained in:
Kemal S 2019-05-24 15:28:34 +02:00
parent b4e7e8b194
commit c26e0113bd
13 changed files with 214 additions and 79 deletions

View File

@ -36,7 +36,10 @@ export class AppStore {
@computed
public get appStatus(): string {
return `The current status is: ${this.status}`;
let result: string = `The current status is '${this.status}'. `;
result += this.status === ApplicationStatus.CreateItems || this.items.length > 0 ? `List '${this.listTitle}' successfully created. ` : "";
result += this.status === ApplicationStatus.Completed ? `In total there were ${this.items.length} items added. ` : "";
return result + `${this.rootStore.configStore.allowImportantItems ? "" : "Adding important items is currently not allowed."}`;
}
@computed
@ -45,7 +48,7 @@ export class AppStore {
}
@computed
public get isLoading(): boolean {
public get isInitializing(): boolean {
return this.isLoadingConfiguration || this.isLoadingOtherStuff;
}
@ -54,6 +57,7 @@ export class AppStore {
return new Promise<void>((resolve, reject) => {
// Mock creating list
setTimeout(() => {
// Make sure we change our state in an action.
runInAction(() => {
this.status = ApplicationStatus.CreateItems;
this.listTitle = listTitle;

View File

@ -10,7 +10,7 @@ export class ConfigStore {
constructor(private rootStore: RootStore) {
this.setInitialState();
// Mock REST call for fetching configuration data, 5 seconds
// Mock REST call for fetching configuration data
setTimeout(() => {
this.loadConfigration();
}, 1000);
@ -19,14 +19,13 @@ export class ConfigStore {
@action
public setInitialState(): void {
this.isLoading = true;
this.allowImportantItems = true;
this.applicationTitle = null;
this.allowImportantItems = false;
}
@action
private loadConfigration() {
this.isLoading = false;
this.allowImportantItems = true;
this.rootStore.appStore.isLoadingConfiguration = false;
}
@ -34,4 +33,9 @@ export class ConfigStore {
public setApplicationTitle(title: string): void {
this.applicationTitle = title;
}
@action
public setAllowImportantItems(allow: boolean): void {
this.allowImportantItems = allow;
}
}

View File

@ -21,7 +21,8 @@
"description": { "default": "An example of a webpart using mobx for managing the application state" },
"officeFabricIconFontName": "Page",
"properties": {
"ApplicationTitle": "Mobx Tutorial Title (change in webpart properties) 😎"
"ApplicationTitle": "Mobx Tutorial Title (change in webpart properties) 😎",
"AllowImportantItems": true
}
}]
}

View File

@ -1,6 +1,6 @@
import { Version } from '@microsoft/sp-core-library';
import { IPropertyPaneConfiguration, PropertyPaneTextField } from '@microsoft/sp-property-pane';
import { BaseClientSideWebPart } from '@microsoft/sp-webpart-base';
import { BaseClientSideWebPart, PropertyPaneCheckbox } from '@microsoft/sp-webpart-base';
import { configure } from "mobx";
import * as strings from 'MobxTutorialWebPartStrings';
import * as React from 'react';
@ -8,10 +8,12 @@ import * as ReactDom from 'react-dom';
import { RootStore } from '../../stores/RootStore';
import MobxTutorialProvider from './components/MobxTutorialProvider';
// State modification should always happen through actions
configure({ enforceActions: "always" });
export interface IMobxTutorialWebPartProps {
ApplicationTitle: string;
AllowImportantItems: boolean;
}
export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTutorialWebPartProps> {
@ -20,6 +22,7 @@ export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTuto
protected onInit() {
return new Promise<void>((resolve, reject) => {
const { configStore } = this.dependencies.rootStore;
configStore.setAllowImportantItems(this.properties.AllowImportantItems);
configStore.setApplicationTitle(this.properties.ApplicationTitle);
resolve();
});
@ -37,10 +40,14 @@ export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTuto
}
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
const { configStore } = this.dependencies.rootStore;
if (propertyPath === "ApplicationTitle") {
const { configStore } = this.dependencies.rootStore;
configStore.setApplicationTitle(newValue);
}
else if (propertyPath === "AllowImportantItems") {
configStore.setAllowImportantItems(newValue);
}
}
protected onDispose(): void {
@ -64,6 +71,9 @@ export default class MobxTutorialWebPart extends BaseClientSideWebPart<IMobxTuto
groupFields: [
PropertyPaneTextField('ApplicationTitle', {
label: strings.AppTitleFieldLabel,
}),
PropertyPaneCheckbox('AllowImportantItems', {
text: strings.AllowImportantItemsLabel
})
]
}

View File

@ -1,21 +1,39 @@
import { toJS } from 'mobx';
import { DetailsList, DetailsListLayoutMode, IColumn } from 'office-ui-fabric-react/lib/DetailsList';
import * as React from 'react';
import { IFakeItem } from '../../../stores/AppStore';
export type DetailedFakeItemViewerProps = {
items: IFakeItem[];
};
export class DetailedFakeItemViewer extends React.Component<DetailedFakeItemViewerProps, {}> {
private _columns: IColumn[];
public state = {
items: []
};
constructor(props: DetailedFakeItemViewerProps) {
super(props);
this._columns = [
{ key: 'Title', name: 'Title', fieldName: 'title', minWidth: 100, maxWidth: 200, isResizable: true },
{ key: 'Important', name: 'Important', fieldName: 'important', minWidth: 100, maxWidth: 150, isResizable: true, onRender: (item: IFakeItem) => { return item.important ? "Yes" : "No"; } }
];
}
public render(): React.ReactElement<DetailedFakeItemViewerProps> {
const { items } = this.props;
return (
<div>
<ul>{items.map(x => <li>{x.title} {x.important ? '!IMPORTANT' : null}</li>)}</ul>
<DetailsList
compact={true}
items={toJS<IFakeItem[]>(items)}
columns={this._columns}
setKey="set"
layoutMode={DetailsListLayoutMode.fixedColumns}
selectionPreservedOnEmptyClick={true}
/>
</div>
);
}

View File

@ -4,6 +4,8 @@ import * as React from 'react';
import { AppStore } from "../../../stores/AppStore";
import { Stores } from '../../../stores/RootStore';
import { DetailedFakeItemViewer } from "./DetailedFakeItemViewer";
import { FakeItemCreator } from "./FakeItemCreator";
import styles from "./MobxTutorial.module.scss";
export type FakeItemContainerStoreProps = {
appStore: AppStore;
@ -18,14 +20,27 @@ export class FakeItemContainer extends React.Component<FakeItemContainerProps, {
public render(): React.ReactElement<FakeItemContainerProps> {
const { appStore } = this.props;
return (
<div>
<PrimaryButton
text="Add Fake Item"
onClick={() => { appStore.addListItem({ title: "dsf", important: true }); }}
allowDisabledFocus={true}
/>
Count items: {appStore.items.length} | Count important items: {appStore.importantItems.length}
<DetailedFakeItemViewer items={appStore.items}></DetailedFakeItemViewer>
<div className={styles.grid}>
<div className={styles.row}>
<PrimaryButton
text="Confirm items"
onClick={() => { appStore.confirmItems(); }}
className={styles.inputElement}
/>
</div>
<div className={styles.row}>
<div className={`${styles.columnCreateItems}`}>
<FakeItemCreator></FakeItemCreator>
</div>
<div className={`${styles.columnItemDetails}`}>
<p>Count items: {appStore.items.length} | Count important items: {appStore.importantItems.length}</p>
<DetailedFakeItemViewer items={appStore.items}></DetailedFakeItemViewer>
</div>
</div>
</div>
);
}

View File

@ -0,0 +1,95 @@
import { inject, observer } from "mobx-react";
import { DefaultButton } from 'office-ui-fabric-react/lib/Button';
import { Checkbox } from 'office-ui-fabric-react/lib/Checkbox';
import { TextField } from "office-ui-fabric-react/lib/TextField";
import * as React from 'react';
import { AppStore } from "../../../stores/AppStore";
import { Stores } from '../../../stores/RootStore';
import styles from "./MobxTutorial.module.scss";
import { ConfigStore } from "../../../stores/ConfigStore";
export type FakeItemCreatorStoreProps = {
appStore: AppStore;
configStore: ConfigStore;
};
export type FakeItemCreatorOwnProps = {
};
export type FakeItemCreatorState = {
itemTitle: string;
isImportant: boolean;
requiredTitle: string;
};
export type FakeItemCreatorProps = Partial<FakeItemCreatorStoreProps> & FakeItemCreatorOwnProps;
const initialState: FakeItemCreatorState = {
itemTitle: "",
isImportant: false,
requiredTitle: undefined
};
@inject(Stores.AppStore, Stores.ConfigurationStore)
@observer
export class FakeItemCreator extends React.Component<FakeItemCreatorProps, FakeItemCreatorState> {
public state = initialState;
public render(): React.ReactElement<FakeItemCreatorProps> {
const { configStore } = this.props;
return (
<div className={styles.row}>
<TextField
label="Title"
errorMessage={this.state.requiredTitle}
onChange={this._onChangeItemTitle}
value={this.state.itemTitle}
className={styles.inputElement}
required
/>
<Checkbox
label="Important?"
onChange={this._onIsImportantCheckboxChange}
className={styles.inputElement}
checked={configStore.allowImportantItems && this.state.isImportant}
disabled={!configStore.allowImportantItems}
/>
<DefaultButton
onClick={this._onAddFakeItem}
iconProps={{ iconName: 'Add' }}
allowDisabledFocus={true}
className={styles.inputElement}
>Add</DefaultButton>
</div>
);
}
private _onAddFakeItem = (): void => {
if (this.state.itemTitle === "" && this.state.itemTitle.length === 0) {
this.setState({ ...this.state, requiredTitle: "Required" });
return;
}
const { appStore, configStore } = this.props;
appStore.addListItem({ title: this.state.itemTitle, important: configStore.allowImportantItems && this.state.isImportant });
this.setState(initialState);
}
private _onChangeItemTitle = (ev: React.FormEvent<HTMLInputElement>, newValue?: string): void => {
if (newValue === "" && newValue.length === 0) {
this.setState({ ...this.state, itemTitle: newValue, requiredTitle: "Required" });
}
else {
this.setState({ ...this.state, itemTitle: newValue, requiredTitle: undefined });
}
}
private _onIsImportantCheckboxChange = (ev: React.FormEvent<HTMLElement>, isChecked: boolean): void => {
this.setState({ ...this.state, isImportant: isChecked });
}
}

View File

@ -5,6 +5,7 @@ import { TextField } from 'office-ui-fabric-react/lib/TextField';
import * as React from 'react';
import { AppStore } from '../../../stores/AppStore';
import { Stores } from "../../../stores/RootStore";
import styles from './MobxTutorial.module.scss';
export type ListCreatorStoreProps = {
appStore: AppStore;
@ -30,17 +31,20 @@ export class ListCreator extends React.Component<ListCreatorProps, ListCreatorSt
const spinner = (<Spinner size={SpinnerSize.xSmall} label="Creating list ..." labelPosition="right" />);
return (
<div>
<div className={styles.row}>
<TextField
label="List title"
errorMessage={this.state.errorMessage}
onChange={this._onChangeListTitle}
value={this.state.listTitle}
disabled={this.state.loading}
required
/>
<PrimaryButton
onClick={() => this.createList()}
disabled={this.state.loading}
className={styles.inputElement}
>
{this.state.loading ? spinner : "Create List"}
</PrimaryButton>
@ -51,7 +55,7 @@ export class ListCreator extends React.Component<ListCreatorProps, ListCreatorSt
public async createList() {
if (this.state.listTitle && this.state.listTitle.length > 0) {
this.setState({ ...this.state, loading: true, errorMessage: undefined });
await this.props.appStore.createList("MOCK TITLE");
await this.props.appStore.createList(this.state.listTitle);
this.setState({ ...this.state, loading: false });
}
else {
@ -66,7 +70,6 @@ export class ListCreator extends React.Component<ListCreatorProps, ListCreatorSt
}
else {
this.setState({ ...this.state, listTitle: newValue });
}
};
}
}

View File

@ -1,23 +1,30 @@
@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';
.mobxTutorial {
.container {
max-width: 700px;
margin: 0px auto;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
margin: 20px;
.grid {
@include ms-Grid;
padding: 10px;
}
.row {
@include ms-Grid-row;
padding: 20px;
}
.column {
.columnCreateItems {
@include ms-Grid-col;
@include ms-lg10;
@include ms-lg4;
@include ms-xl4;
@include ms-sm12;
padding-right: 50px;
}
.columnItemDetails {
@include ms-Grid-col;
@include ms-lg8;
@include ms-xl8;
@include ms-xlPush2;
@include ms-lgPush1;
@include ms-sm12;
}
.title {
@ -28,38 +35,7 @@
@include ms-font-l;
}
.button {
// Our button
text-decoration: none;
height: 32px;
// Primary Button
min-width: 80px;
background-color: $ms-color-themePrimary;
border-color: $ms-color-themePrimary;
color: $ms-color-white;
// Basic Button
outline: transparent;
position: relative;
font-family: "Segoe UI WestEuropean", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
font-size: $ms-font-size-m;
font-weight: $ms-font-weight-regular;
border-width: 0;
text-align: center;
cursor: pointer;
display: inline-block;
padding: 0 16px;
.label {
font-weight: $ms-font-weight-semibold;
font-size: $ms-font-size-m;
height: 32px;
line-height: 32px;
margin: 0 4px;
vertical-align: top;
display: inline-block;
}
.inputElement {
margin-top: 5px;
}
}

View File

@ -1,13 +1,13 @@
import { inject, observer } from 'mobx-react';
import { Spinner, SpinnerSize } from 'office-ui-fabric-react/lib/Spinner';
import * as React from 'react';
import { AppStore } from '../../../stores/AppStore';
import { ApplicationStatus, AppStore } from '../../../stores/AppStore';
import { ConfigStore } from '../../../stores/ConfigStore';
import { Stores } from '../../../stores/RootStore';
import { FakeItemContainer } from './FakeItemContainer';
import { ProgressIndicator } from './ProgressIndicator';
import { ListCreator } from './ListCreator';
import styles from './MobxTutorial.module.scss';
import { ProgressIndicator } from './ProgressIndicator';
export type MobxTutorialStoreProps = {
appStore: AppStore;
@ -22,26 +22,33 @@ export class MobxTutorial extends React.Component<MobxTutorialProps, {}> {
public render(): React.ReactElement<MobxTutorialProps> {
const { appStore, configStore } = this.props;
if (appStore.isLoading)
return (<Spinner size={SpinnerSize.large} label="Loading... please hodl" ariaLive="assertive" labelPosition="left" />);
if (appStore.isInitializing)
return (<Spinner size={SpinnerSize.large} label="Initializing... please hodl" ariaLive="assertive" labelPosition="left" />);
return (
<div className={styles.mobxTutorial}>
<div className={styles.row}>
<div className={styles.title}>{configStore.applicationTitle}</div>
<ProgressIndicator></ProgressIndicator>
</div>
<div className={styles.row}>
<div className={styles.subTitle}>1) Create List</div>
<ListCreator></ListCreator>
</div>
{appStore.status === ApplicationStatus.CreateList ?
<div className={styles.row}>
<div className={styles.subTitle}>1) Create List</div>
<ListCreator></ListCreator>
</div>
:
null
}
<div className={styles.row}>
<div className={styles.subTitle}>2) Create Items</div>
<FakeItemContainer></FakeItemContainer>
</div>
{appStore.status === ApplicationStatus.CreateItems ?
<div className={styles.row}>
<div className={styles.subTitle}>2) Create Items</div>
<FakeItemContainer></FakeItemContainer>
</div>
:
null
}
</div>
);

View File

@ -15,6 +15,6 @@ export type ProgressIndicatorProps = Partial<ProgressIndicatorStoreProps> & Prog
export class ProgressIndicator extends React.Component<ProgressIndicatorProps, {}> {
public render(): React.ReactElement<ProgressIndicatorProps> {
const { appStore } = this.props;
return (<>{appStore.appStatus}</>);
return (<p>{appStore.appStatus}</p>);
}
}

View File

@ -2,6 +2,7 @@ define([], function() {
return {
"PropertyPaneDescription": "Application configuration",
"BasicGroupName": "Group Name",
"AppTitleFieldLabel": "Application Title Field"
"AppTitleFieldLabel": "Application Title Field",
"AllowImportantItemsLabel": "Allow important items"
}
});

View File

@ -2,6 +2,7 @@ declare interface IMobxTutorialWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
AppTitleFieldLabel: string;
AllowImportantItemsLabel: string;
}
declare module 'MobxTutorialWebPartStrings' {