diff --git a/samples/react-multilist-grid/src/images/ListDefinitions.PNG b/samples/react-multilist-grid/src/images/ListDefinitions.PNG index fc9fe87cc..d448eaf70 100644 Binary files a/samples/react-multilist-grid/src/images/ListDefinitions.PNG and b/samples/react-multilist-grid/src/images/ListDefinitions.PNG differ diff --git a/samples/react-multilist-grid/src/images/columnDefinitions.PNG b/samples/react-multilist-grid/src/images/columnDefinitions.PNG index 28131a711..14847f096 100644 Binary files a/samples/react-multilist-grid/src/images/columnDefinitions.PNG and b/samples/react-multilist-grid/src/images/columnDefinitions.PNG differ diff --git a/samples/react-multilist-grid/src/images/editListItems.PNG b/samples/react-multilist-grid/src/images/editListItems.PNG index 4cfae620c..f0834e251 100644 Binary files a/samples/react-multilist-grid/src/images/editListItems.PNG and b/samples/react-multilist-grid/src/images/editListItems.PNG differ diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/ISpfxReactGridWebPartProps.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/ISpfxReactGridWebPartProps.ts index fa53d5d87..b65826a9d 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/ISpfxReactGridWebPartProps.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/ISpfxReactGridWebPartProps.ts @@ -1,8 +1,8 @@ import ListDefinition from "./model/ListDefinition"; -import column from "./model/ColumnDefinition"; +import ColumnDefinition from "./model/ColumnDefinition"; export interface ISpfxReactGridWebPartProps { description: string; lists: ListDefinition[]; - columns: column[]; + columns: ColumnDefinition[]; } diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx index a52b94d20..051c0ea0b 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx @@ -1,29 +1,21 @@ import * as React from "react"; import * as ReactDom from "react-dom"; import { Provider } from "react-redux"; - -// require("office-ui-fabric/dist/css/Fabric.css"); -// require("office-ui-fabric/dist/css/Fabric.Components.css"); -// require("office-ui-fabric/dist/components/CommandBar/CommandBar.css"); - - import configureStore from "./store/configure-store"; const { Router, createMemoryHistory } = require("react-router"); - -import { addLists } from "./actions/listActions"; -import { addColumns } from "./actions/columnActions"; -import { addPageContext } from "./actions/PageContextActions"; +import { addColumns, removeAllColumns } from "./actions/columnActions"; +import { addLists, removeAllLists } from "./actions/listActions"; +import { PropertyFieldColumnDefinitions, IPropertyFieldColumnDefinitionsProps } from "./containers/PropertyFieldColumnDefinitions"; +import { PropertyFieldListDefinitions, IPropertyFieldListDefinitionsProps } from "./containers/PropertyFieldListDefinitions"; import { BaseClientSideWebPart, - IPropertyPaneSettings, IWebPartData, IHtmlProperties, + IPropertyPaneSettings, IWebPartContext, PropertyPaneTextField } from "@microsoft/sp-webpart-base"; import { Log } from "@microsoft/sp-client-base"; import routes from "./store/routes"; import * as strings from "spfxReactGridStrings"; - - import { ISpfxReactGridWebPartProps } from "./ISpfxReactGridWebPartProps"; const store: Redux.Store = configureStore({}); const history = createMemoryHistory(location); @@ -35,31 +27,64 @@ const App: React.StatelessComponent = () => ( ); export default class SpfxReactGridWebPart extends BaseClientSideWebPart { + + private cdProps: IPropertyFieldColumnDefinitionsProps; + private ldProps: IPropertyFieldListDefinitionsProps; public constructor(context: IWebPartContext) { super(context); - Log.verbose("SpfxReactGridWebPart", "In constructor of SpfxReactGridWebPart"); + this.onPropertyChange = this.onPropertyChange.bind(this); + + this.cdProps = { + label: strings.ColumnDefinitionFieldLabel, + onPropertyChange: this.onPropertyChange, + getColumnDefinitions: ()=>{ + return this.properties.columns||[]; + }, + }; + this.ldProps = { + label: strings.ListDefinitionFieldLabel, + onPropertyChange: this.onPropertyChange, + getColumnDefinitions: ()=>{ + return this.properties.columns||[]; + }, + getListDefinitions:() =>{ + return this.properties.lists||[]; + }, + PageContext: this.context.pageContext + }; + } + public onInit(): Promise { + + return Promise.resolve(null); } public render(): void { store.dispatch(addLists(this.properties.lists)); store.dispatch(addColumns(this.properties.columns)); - store.dispatch(addPageContext(this.context.pageContext)); + Log.verbose("SpfxReactGridWebPart", "In render of SpfxReactGridWebPart"); ReactDom.render(App(), this.domElement); } - protected deserialize(data: IWebPartData): ISpfxReactGridWebPartProps { - const info = super.deserialize(data); - return info; - } - protected onBeforeSerialize(): IHtmlProperties { - // this.properties.columns = []; - // this.properties.lists =[]; - this.properties.columns = store.getState().columns; - this.properties.lists = store.getState().lists; - return super.onBeforeSerialize(); - } + private onPropertyChange(propertyPath: string, oldValue: any, newValue: any) { + + switch (propertyPath) { + case "ColumnDefinitions": + this.properties.columns = newValue; + store.dispatch(removeAllColumns()); + store.dispatch(addColumns(this.properties.columns)); + break; + case "ListDefinitions": + this.properties.lists = newValue; + store.dispatch(removeAllLists()); + store.dispatch(addLists(this.properties.lists)); + break; + default: + break; + } + }; protected get propertyPaneSettings(): IPropertyPaneSettings { + Log.verbose("SpfxReactGridWebPart", "In propertyPaneSettings of SpfxReactGridWebPart"); return { pages: [ @@ -73,7 +98,9 @@ export default class SpfxReactGridWebPart extends BaseClientSideWebPart = [ { key: null, text: "(Selecte one)" }, - { key: "__LISTDEFINITIONTITLE__", text: "List Title" }, //used to display the ListDefinition Title in the grid, for when users add a new item + { key: "__LISTDEFINITIONTITLE__", text: "List Title" }, //used to display the ListDefinition Title in the grid, for when users add a new item { key: "Text", text: "Text" }, { key: "Integer", text: "Integer" }, { key: "Note", text: "Note" }, @@ -36,14 +46,15 @@ const fieldTypes: Array = [ // { name: "WorkflowEventType", value: "WorkflowEventType" }, ]; -interface IColumnsPageProps extends React.Props { +export interface IColumnsPageProps extends React.Props { columns: Array; addColumn: () => void; removeAllColumns: () => void; removeColumn: (column) => void; - saveColumn: (Column) => void; + moveColumnUp: (Column: ColumnDefinition) => void; moveColumnDown: (Column: ColumnDefinition) => void; + save: () => void; } interface IContextMenu extends React.Props { onRowDelete: AdazzleReactDataGrid.ColumnEventCallback; @@ -60,9 +71,7 @@ function mapDispatchToProps(dispatch) { const col: ColumnDefinition = new ColumnDefinition(id.toString(), "", 80, true); dispatch(addColumn(col)); }, - saveColumn: (updatedRowData): void => { - dispatch(saveColumn(updatedRowData)); - }, + removeColumn: (column): void => { dispatch(removeColumn(column)); @@ -77,6 +86,9 @@ function mapDispatchToProps(dispatch) { moveColumnDown: (column): void => { dispatch(moveCulumnDown(column)); }, + save: (column): void => { + // // dispatch(moveCulumnDown(column)); + }, }; } @@ -88,13 +100,13 @@ export interface GridColumn { formatter?: string; editor?: string; } -interface IGridProps { +export interface IGridProps { editing: { entityid: string; columnid: string; }; } -class ColumnDefinitionContainer extends React.Component { +export class ColumnDefinitionContainerNative extends React.Component { public constructor() { super(); this.CellContents = this.CellContents.bind(this); @@ -108,6 +120,7 @@ class ColumnDefinitionContainer extends React.Component = [{ id: "guid", @@ -146,7 +159,7 @@ class ColumnDefinitionContainer extends React.Component temp.guid === entityid); const column = this.gridColulumns.find(temp => temp.id === columnid); entity[column.name] = value; - this.props.saveColumn(entity); + // this.props.saveColumn(entity); } @@ -308,7 +321,6 @@ class ColumnDefinitionContainer extends React.Component -

Column Definitions

+ }, + { + key: "save", + name: "save", + canCheck: true, + icon: "Save", + onClick: this.props.save + } + + ]} /> @@ -341,4 +362,4 @@ class ColumnDefinitionContainer extends React.Component { +export interface IListViewPageProps extends React.Props { lists: Array; columnRefs: Array; sites: Array; @@ -36,6 +36,7 @@ interface IListViewPageProps extends React.Props { getWebs: (siteUrl) => Promise; getListsForWeb: (webUrl) => Promise; getFieldsForList: (webUrl, listId) => Promise; + save: () => void; pageContext: PageContext; } function mapStateToProps(state) { @@ -75,13 +76,13 @@ function mapDispatchToProps(dispatch) { }, }; } -interface IGridProps { +export interface IGridProps { editing: { entityid: string; columnid: string; }; } -class ListDefinitionContainer extends React.Component { +export class ListDefinitionContainerNative extends React.Component { public defaultColumns: Array = [ { id: "rowGuid", @@ -208,7 +209,7 @@ class ListDefinitionContainer extends React.Component); case "ListEditor": @@ -387,7 +389,7 @@ class ListDefinitionContainer extends React.Component, cellUpdated: (newValue) => void, cellUpdatedEvent: (event: React.SyntheticEvent) => void; }): JSX.Element { const {entity, columns, cellUpdated, cellUpdatedEvent} = props; - debugger; + return ( { @@ -422,12 +424,10 @@ class ListDefinitionContainer extends React.Component -

List Definitions

- -
@@ -469,4 +476,4 @@ debugger; export default connect( mapStateToProps, mapDispatchToProps -)(ListDefinitionContainer); +)(ListDefinitionContainerNative); diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainer.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainer.tsx index 825197f50..bd8c2c3b7 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainer.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainer.tsx @@ -167,6 +167,14 @@ class ListItemContainer extends React.Component this.props.getListItems(this.props.listDefinitions); } + public componentWillReceiveProps(newProps: IListViewPageProps) { + + if (newProps.listDefinitions === this.props.listDefinitions && newProps.columns === this.props.columns) { + return; + } + + this.props.getListItems(this.props.listDefinitions); + } /** * Method to get the parent TD of any cell, * The listItemId and columnID are stored as attributes of the cells parent TD. @@ -323,25 +331,26 @@ class ListItemContainer extends React.Component const {entityid, columnid} = this.state.editing; const entity: ListItem = this.props.listItems.find((temp) => temp.GUID === entityid); const listDef = this.getListDefinition(entity.__metadata__ListDefinitionId); - const titlecolumnid = this.props.columns.find(c => { return c.type === "__LISTDEFINITIONTITLE__"; }).guid; - if (columnid === titlecolumnid) { // user just changed the listDef, - - if (entity.__metadata__GridRowStatus === GridRowStatus.pristine) { - if (!entity.__metadata__OriginalValues) { //SAVE orgininal values so we can undo; - entity.__metadata__OriginalValues = _.cloneDeep(entity); // need deep if we have lookup values + const titleColumn = this.props.columns.find(c => { return c.type === "__LISTDEFINITIONTITLE__"; }); + if (titleColumn) { + if (columnid === titleColumn.guid) { // user just changed the listDef, + if (entity.__metadata__GridRowStatus === GridRowStatus.pristine) { + if (!entity.__metadata__OriginalValues) { //SAVE orgininal values so we can undo; + entity.__metadata__OriginalValues = _.cloneDeep(entity); // need deep if we have lookup values + } } - } - entity.__metadata__ListDefinitionId = value.key; // value is a DropDDownOptions - if (entity.__metadata__GridRowStatus !== GridRowStatus.new) { - const newListDef = this.getListDefinition(value.key); - this.props.getSiteUsersAction(newListDef.siteUrl).then(r => { - this.mapOldListFieldsToNewListFields(entity); + entity.__metadata__ListDefinitionId = value.key; // value is a DropDDownOptions + if (entity.__metadata__GridRowStatus !== GridRowStatus.new) { + const newListDef = this.getListDefinition(value.key); + this.props.getSiteUsersAction(newListDef.siteUrl).then(r => { + this.mapOldListFieldsToNewListFields(entity); + this.props.saveListItem(entity); + }); + } else { this.props.saveListItem(entity); - }); - } else { - this.props.saveListItem(entity); + } + return; } - return; } const columnReference = listDef.columnReferences.find(cr => cr.columnDefinitionId === columnid); const internalName = utils.ParseSPField(columnReference.name).id; @@ -363,8 +372,16 @@ class ListItemContainer extends React.Component entity[internalName] = value.text; break; case "DateTime": - - entity[internalName] = (value.getFullYear().toString()) + "-" + (value.getMonth() + 1).toString() + "-" + value.getDate().toString() + "T00:00:00Z"; + let year = value.getFullYear().toString(); + let month = (value.getMonth() + 1).toString(); + let day = value.getDate().toString(); + if (month.length === 1) { + month = "0" + month; + } + if (day.length === 1) { + day = "0" + day; + } + entity[internalName] = year + "-" + month + "-" + day + "T00:00:00Z"; break; case "Lookup": if (!entity[internalName]) {// if value was not previously set , then this is undefined// @@ -583,7 +600,7 @@ class ListItemContainer extends React.Component goToToday: "yes" }; let date = null; - if (columnValue !== null) { + if (columnValue) { const year = parseInt(columnValue.substring(0, 34)); const month = parseInt(columnValue.substring(5, 7)) - 1; const day = parseInt(columnValue.substring(8, 10)); diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainerUIF.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainerUIF.tsx deleted file mode 100644 index 71ffa0bb2..000000000 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainerUIF.tsx +++ /dev/null @@ -1,761 +0,0 @@ -// import * as utils from "../utils/utils"; -// import * as React from "react"; -// const connect = require("react-redux").connect; -// import { -// addListItem, removeListItem, getListItemsAction, saveListItemAction, -// undoListItemChangesAction, updateListItemAction, -// } from "../actions/listItemActions"; -// import { getLookupOptionAction } from "../actions/lookupOptionsActions"; -// import { getSiteUsersAction } from "../actions/siteUsersActions"; -// import ListItem from "../model/ListItem"; -// import ColumnDefinition from "../model/ColumnDefinition"; -// import { LookupOptions, LookupOptionStatus } from "../model/LookupOptions"; -// import { SiteUsers, SiteUsersStatus } from "../model/SiteUsers"; -// import GridRowStatus from "../model/GridRowStatus"; -// import ListDefinition from "../model/ListDefinition"; -// import { FocusZone,Button, ButtonType, TextField, IDropdownOption, Dropdown, Spinner, SpinnerType, DetailsList, IColumn } from "office-ui-fabric-react"; - -// import { CommandBar } from "office-ui-fabric-react/lib/CommandBar"; -// import { DatePicker, IDatePickerStrings } from "office-ui-fabric-react/lib/DatePicker"; - -// import Container from "../components/container"; -// import { Log } from "@microsoft/sp-client-base"; - -// interface SPColumn extends IColumn { -// editable: boolean; -// type: string; -// } -// interface IListViewPageProps extends React.Props { -// /** An array of ListItems fetched from sharepoint */ -// siteUsers: Array; -// /** An array of ListItems fetched from sharepoint */ -// listItems: Array; -// /** An array of LookupOptions. One for each Lookup Column */ -// lookupOptions: Array; -// /** An array of columns to be displayed on the grid */ -// columns: Array; -// /** The listDefinitions. Says which lists to pull data from */ -// listDefinitions: Array; -// /** Redux Action to add a new listitem */ -// addListItem: (ListItem) => void; -// /** Redux Action to add a new remove a list item */ -// removeListItem: (l: ListItem, ListDef: ListDefinition) => void; -// /** Redux Action to get listitems from a specific list */ -// getListItems: (listDefinitions: Array) => void; -// /** Redux Action to update a listitem in sharepoint */ -// updateListItem: (ListItem: ListItem, ListDef: ListDefinition) => Promise; -// /** Redux Action to get the lookup options for a specific field */ -// getLookupOptionAction: (lookupSite, lookupWebId, lookupListId, lookupField) => void; -// /** Redux Action to get the lookup options for a specific field */ -// getSiteUsersAction: (site) => Promise; -// /** Redux Action to undo changes made to the listitem */ -// undoItemChanges: (ListItem) => void; -// /** Redux Action to save the listitem in the store (NOT to sharepoint*/ -// saveListItem: (ListItem) => void; -// } -// function mapStateToProps(state) { - -// return { -// listItems: state.items, -// columns: state.columns, -// listDefinitions: state.lists, -// systemStatus: state.systemStatus, -// lookupOptions: state.lookupOptions, -// siteUsers: state.siteUsers -// }; -// } -// export class GridColumn { -// constructor( -// public id: string, -// public name: string, -// public editable: boolean, -// public width: number, -// public formatter: string = "", -// public editor?: string) { } -// } -// function mapDispatchToProps(dispatch) { -// return { -// addListItem: (listItem: ListItem): void => { -// dispatch(addListItem(listItem)); -// }, -// removeListItem: (listItem: ListItem, listDef: ListDefinition): void => { -// dispatch(removeListItem(dispatch, listItem, listDef)); -// }, -// updateListItem: (listItem: ListItem, listDef: ListDefinition): Promise => { -// const action = updateListItemAction(dispatch, listDef, listItem); -// dispatch(action); // need to ewname this one to be digfferent from the omported ome -// return action.payload.promise; - -// }, -// saveListItem: (listItem: ListItem): void => { -// dispatch(saveListItemAction(listItem)); -// }, -// undoItemChanges: (listItem: ListItem): void => { -// dispatch(undoListItemChangesAction(listItem)); -// }, - -// getListItems: (listDefinitions: Array): void => { -// dispatch(getListItemsAction(dispatch, listDefinitions)); -// }, -// getLookupOptionAction: (lookupSite, lookupWebId, lookupListId, lookupField): void => { -// dispatch(getLookupOptionAction(dispatch, lookupSite, lookupWebId, lookupListId, lookupField)); -// }, -// getSiteUsersAction: (site): void => { - -// const action = getSiteUsersAction(dispatch, site); -// dispatch(action); -// return action.payload.promise; -// }, -// }; -// } -// /** -// * -// */ -// interface IGridState { -// editing: { -// /**The Sharepoint GUID of the listitem being edited */ -// entityid: string; -// /**The id of the column being edited */ -// columnid: string; -// }; -// } -// /** -// * This component is the Grid for editing listitems. -// */ -// class ListItemContainerUIF extends React.Component { -// public constructor() { -// super(); -// this.CellContentsEditable = this.CellContentsEditable.bind(this); -// this.CellContents = this.CellContents.bind(this); -// this.TableDetail = this.TableDetail.bind(this); -// this.toggleEditing = this.toggleEditing.bind(this); -// this.addListItem = this.addListItem.bind(this); -// this.removeListItem = this.removeListItem.bind(this); -// this.handleCellUpdated = this.handleCellUpdated.bind(this); -// this.handleCellUpdatedEvent = this.handleCellUpdatedEvent.bind(this); -// this.undoItemChanges = this.undoItemChanges.bind(this); -// this.updateListItem = this.updateListItem.bind(this); -// this.getLookupOptions = this.getLookupOptions.bind(this); -// this.onRenderItemColumn = this.onRenderItemColumn.bind(this); - - -// } -// private addListItem(): void { - -// let listItem = new ListItem(); -// for (const column of this.props.columns) { -// listItem[column.name] === null; -// } -// if (this.props.listDefinitions.length === 1) { -// listItem.__metadata__ListDefinitionId = this.props.listDefinitions[0].guid; -// } else { -// listItem.__metadata__ListDefinitionId = null; -// } - -// this.props.addListItem(listItem); -// } -// private removeListItem(event): void { - -// const parentTD = this.getParent(event.target, "DIV", "spCell"); -// const attributes: NamedNodeMap = parentTD.attributes; -// const entityid = attributes.getNamedItem("data-entityid").value; // theid of the SPListItem -// const listItem: ListItem = this.props.listItems.find((temp) => temp.GUID === entityid); // the listItemItself -// const listDef = this.getListDefinition(listItem.__metadata__ListDefinitionId);// The list Definition this item is associated with. -// this.props.removeListItem(listItem, listDef); -// } -// /** -// * When the component Mounts, call an action to get the listitems for all the listdefinitions -// */ -// public componentWillMount() { - -// this.props.getListItems(this.props.listDefinitions); -// } -// /** -// * Method to get the parent span of any cell whose classnAME IS spcell -// * The listItemId and columnID are stored as attributes of THAT SPAN -// */ -// public getParent(node: Node, type: string, className: string): Node { - -// while (node.nodeName !== type && node["className"] !== className) { -// node = node.parentNode; -// } -// return node; -// } -// /** -// * This event gets fired whenever a cell on the grid recieves focus. -// * The "editing" propery of this component determines which cell is being edited. -// * This method gets the clicked on (the entityid-- the id of the SPLIstItem) and the columnId (the id of the ColumnDefinition) -// * and sets them in the "editing"property of state. -// * When Component then redraws, it draws that cell as an editable (See the TableDetail method). -// * -// * If the rendering of that column in edit mode requires additional Info, dispatch a redux action to get the data. -// * (Dispatching the action from within the render mehod itself cused infinite loop) -// * -// */ -// public toggleEditing(event) { -// Log.verbose("list-Page", "focus event fired editing when entering cell"); -// const target = this.getParent(event.target, "DIV", "spCell"); // walk up the Dom to the TD, thats where the IDs are stored -// const attributes: NamedNodeMap = target.attributes; -// const entityid = attributes.getNamedItem("data-entityid").value; -// const columnid = attributes.getNamedItem("data-columnid").value; -// if (columnid != "") { //user clicked on a column, not a button( Buttons are in a td with am column id of "" -// /** -// * Need to fire events here to get data needed for the rerender -// */ -// const listitem = this.props.listItems.find(li => li.GUID === entityid); -// const listDef = this.getListDefinition(listitem.__metadata__ListDefinitionId); -// if (listDef) {// if user just added an item we may not hava a lisdef yest -// const colref = listDef.columnReferences.find(cr => cr.columnDefinitionId === columnid); -// if (colref) {// Listname does not have a columnReference - -// switch (colref.fieldDefinition.TypeAsString) { -// case "Lookup": -// let lookupField = colref.fieldDefinition.LookupField; -// let lookupListId = colref.fieldDefinition.LookupList; -// let lookupWebId = colref.fieldDefinition.LookupWebId; -// /** -// * We are assuming here that the lookup listy is in the same web. -// * -// */ -// lookupWebId = utils.ParseSPField(listDef.webLookup).id; // temp fix. Need to use graph to get the web by id in the site -// let lookupSite = listDef.siteUrl; -// this.ensureLookupOptions(lookupSite, lookupWebId, lookupListId, lookupField); -// break; -// case "User": - -// lookupWebId = utils.ParseSPField(listDef.webLookup).id; // temp fix. Need to use graph to get the web by id in the site -// let site = listDef.siteUrl; -// this.ensureSiteUsers(site); -// break; -// default: -// break; -// } -// } -// } -// } -// this.setState({ "editing": { entityid: entityid, columnid: columnid } }); -// } -// /** -// * This event gets fired to revert any changes made to the ListItem. -// */ -// public undoItemChanges(event): void { -// const parentTD = this.getParent(event.target, "DIV", "spCell"); // the listitemId and the column ID are always stored as attributes of the parent TD. -// const attributes: NamedNodeMap = parentTD.attributes; -// const entityitem = attributes.getNamedItem("data-entityid"); -// const entityid = entityitem.value; -// const entity: ListItem = this.props.listItems.find((temp) => temp.GUID === entityid); -// this.props.undoItemChanges(entity); -// } -// /** -// * This event gets fired, to save the item back to SharePoint. -// */ -// public updateListItem(event): void { - -// const parentTD = this.getParent(event.target, "DIV", "spCell"); -// const attributes: NamedNodeMap = parentTD.attributes; -// const entityid = attributes.getNamedItem("data-entityid").value; // theid of the SPListItem -// const entity: ListItem = this.props.listItems.find((temp) => temp.GUID === entityid); -// const listDef: ListDefinition = this.getListDefinition(entity.__metadata__ListDefinitionId); -// if (entity.__metadata__ListDefinitionId === entity.__metadata__OriginalValues.__metadata__ListDefinitionId -// || entity.__metadata__GridRowStatus === GridRowStatus.new) {// List not changed - -// this.props.updateListItem(entity, listDef); -// } -// else {// list changed, add to new, delete from old (will need to do some fiorld mapping in here -// entity.__metadata__GridRowStatus = GridRowStatus.new; -// this.props.updateListItem(entity, listDef).then(response => { -// const oldListDef: ListDefinition = this.getListDefinition(entity.__metadata__OriginalValues.__metadata__ListDefinitionId); -// this.props.removeListItem(entity.__metadata__OriginalValues, oldListDef); -// }); -// } - -// } -// /** -// * This method gets called when react events are used to update a cell in the grid. -// * It just gets the value and passes it to handleCellUpdated. -// */ -// private handleCellUpdatedEvent(event) { //native react uses a Synthetic event -// this.handleCellUpdated(event.target.value); - -// } -// /** -// * This method gets called when user changes the listdefinition for an item. -// * the old fields are moved to the corresponing new fields and translated as needed -// */ -// private mapOldListFieldsToNewListFields(listItem: ListItem) { - -// const newListDef = this.getListDefinition(listItem.__metadata__ListDefinitionId); -// const oldListDef = this.getListDefinition(listItem.__metadata__OriginalValues.__metadata__ListDefinitionId); -// for (const newColRef of newListDef.columnReferences) { -// // find the old columnReference -// const oldColRef = oldListDef.columnReferences.find(cr => cr.columnDefinitionId === newColRef.columnDefinitionId); -// const newFieldName = utils.ParseSPField(newColRef.name).id; -// const oldFieldName = utils.ParseSPField(oldColRef.name).id; -// switch (newColRef.fieldDefinition.TypeAsString) { -// case "User": -// // should male a local copy befor i start messing with these.// fieldd names may overlap on old and new -// // const name = listItem.__metadata__OriginalValues[oldFieldName].Name;// the user login name -// const name = listItem[oldFieldName].Name;// the user login name -// const siteUsersOnNewSite = this.props.siteUsers.find(su => su.siteUrl === newListDef.siteUrl); -// const newUser = siteUsersOnNewSite.siteUser.find(user => user.loginName === name); -// listItem[newFieldName].Id = newUser.id; -// listItem[newFieldName].Name = newUser.loginName; -// listItem[newFieldName].Title = newUser.value; -// break; - -// default: -// listItem[newFieldName] = listItem[oldFieldName]; -// } - -// } -// } -// /** -// * This method gets called when react cells in the gid get updated. -// * Office UI Fabric does not use events. It just calls this method with the new value. -// * It reformats the data to fit the format we recievbed from SP in the first place , -// * and dispatches an action to save the data in the store. -// * -// * Also, it saves the original version of the record, so we can undo later. -// */ -// private handleCellUpdated(value) { - -// const {entityid, columnid} = this.state.editing; -// const entity: ListItem = this.props.listItems.find((temp) => temp.GUID === entityid); -// const listDef = this.getListDefinition(entity.__metadata__ListDefinitionId); -// const titlecolumnid = this.props.columns.find(c => { return c.type === "__LISTDEFINITIONTITLE__" }).guid -// if (columnid === titlecolumnid) { // user just changed the listDef, - -// if (entity.__metadata__GridRowStatus === GridRowStatus.pristine) { -// if (!entity.__metadata__OriginalValues) { //SAVE orgininal values so we can undo; -// entity.__metadata__OriginalValues = _.cloneDeep(entity); // need deep if we have lookup values -// } -// } -// entity.__metadata__ListDefinitionId = value.key; // value is a DropDDownOptions -// if (entity.__metadata__GridRowStatus !== GridRowStatus.new) { -// const listDef = this.getListDefinition(value.key); -// this.props.getSiteUsersAction(listDef.siteUrl).then(r => { -// this.mapOldListFieldsToNewListFields(entity); -// this.props.saveListItem(entity); -// }); -// } else { -// this.props.saveListItem(entity); -// } -// return; -// } -// const columnReference = listDef.columnReferences.find(cr => cr.columnDefinitionId === columnid); -// const internalName = utils.ParseSPField(columnReference.name).id; -// if (!entity.__metadata__OriginalValues) { //SAVE orgininal values so we can undo; -// entity.__metadata__OriginalValues = _.cloneDeep(entity); // need deep if we have lookup values -// } -// if (entity.__metadata__GridRowStatus === GridRowStatus.pristine) { -// entity.__metadata__GridRowStatus = GridRowStatus.modified; -// } -// switch (columnReference.fieldDefinition.TypeAsString) { -// case "User": -// if (!entity[internalName]) {// if value was not previously set , then this is undefined// -// entity[internalName] = {};// set new value to an empty objecte -// } -// entity[internalName].Id = value.key;//and then fill in the values -// entity[internalName].Title = value.text; -// break; -// case "DateTime": -// const year = value.getFullYear().toString(); - -// entity[internalName] = (value.getFullYear().toString()) + "-" + (value.getMonth() + 1).toString() + "-" + value.getDate().toString() + "T00:00:00Z"; -// break; -// case "Lookup": -// if (!entity[internalName]) {// if value was not previously set , then this is undefined// -// entity[internalName] = {};// set new value to an empty objecte -// } -// entity[internalName]["Id"] = value.key;//and then fill in the values -// entity[internalName][columnReference.fieldDefinition.LookupField] = value.text; -// break; -// default: -// entity[internalName] = value; -// } -// this.props.saveListItem(entity); -// } -// /** -// * If the the options for a lookup list are not in the cache, fire an event to get them -// * This method is called when a lookup column receives focus. -// */ -// public ensureSiteUsers(siteUrl: string): SiteUsers { -// // see if the options are in the store, if so, return them, otherwoise dispatch an action to get them -// const siteUsers = this.props.siteUsers.find(x => { -// return (x.siteUrl === siteUrl); -// }); -// if (siteUsers === undefined) { -// this.props.getSiteUsersAction(siteUrl); -// } -// return siteUsers; -// } -// /** -// * Gets the options to display for a lookupField -// * This method is called when a lookup column gets rendered... we fire the event to get the data when its focused, -// * then we use the data when it gets renderd -// */ -// public getSiteUsers(siteUrl: string): SiteUsers { -// // see if the options are in the store, if so, return them, otherwoise dispatch an action to get them -// const siteUsers = this.props.siteUsers.find(x => { -// return (x.siteUrl === siteUrl); -// }); -// return siteUsers; -// } -// /** -// * If the the options for a lookup list are not in the cache, fire an event to get them -// * This method is called when a lookup column receives focus. -// */ -// public ensureLookupOptions(lookupSite: string, lookupWebId: string, lookupListId: string, lookupField: string): LookupOptions { -// // see if the options are in the store, if so, return them, otherwoise dispatch an action to get them -// const lookupoptions = this.props.lookupOptions.find(x => { -// return (x.lookupField === lookupField) && -// (x.lookupListId === lookupListId) && -// (x.lookupSite === lookupSite) && -// (x.lookupWebId === lookupWebId); -// }); -// if (lookupoptions === undefined) { -// this.props.getLookupOptionAction(lookupSite, lookupWebId, lookupListId, lookupField); -// } -// return lookupoptions; -// } -// /** -// * Gets the options to display for a lookupField -// * This method is called when a lookup column gets rendered... we fire the event to get the data when its focused, -// * then we use the data when it gets renderd -// */ -// public getLookupOptions(lookupSite: string, lookupWebId: string, lookupListId: string, lookupField: string): LookupOptions { -// // see if the options are in the store, if so, return them, otherwoise dispatch an action to get them -// let lookupoptions = this.props.lookupOptions.find(x => { -// return (x.lookupField === lookupField) && -// (x.lookupListId === lookupListId) && -// (x.lookupSite === lookupSite) && -// (x.lookupWebId === lookupWebId); -// }); -// return lookupoptions; -// } -// /** -// * Returns the ListDefinition for the given ListDefinionId -// * -// */ -// public getListDefinition( -// /** The id of the list definition to be retrieved */ -// listdefid: string -// ): ListDefinition { -// return this.props.listDefinitions.find(ld => ld.guid === listdefid); -// } -// /** -// * This method renders the contents of an individual cell in an editable format. -// */ -// public CellContentsEditable(props: { entity: ListItem, column: SPColumn, cellUpdated: (newValue) => void, cellUpdatedEvent: (event: React.SyntheticEvent) => void; }): JSX.Element { - -// const {entity, column, cellUpdated, cellUpdatedEvent} = props; - -// if (column.type === "__LISTDEFINITIONTITLE__") { -// entity.__metadata__ListDefinitionId -// const opts: Array = this.props.listDefinitions.map(ld => { -// return { key: ld.guid, text: ld.title }; -// }); -// // if (!entity.__metadata__ListDefinitionId) { -// // opts.unshift({ key: null, text: "Select one" }); -// // } -// // should I have a different handler for this? -// return ( -// { cellUpdated(selection); } } /> -// ); -// } -// const listDef = this.getListDefinition(entity.__metadata__ListDefinitionId); -// const colref = listDef.columnReferences.find(cr => cr.columnDefinitionId === column.key); -// const internalName = utils.ParseSPField(colref.name).id; -// const columnValue = entity[internalName]; -// switch (colref.fieldDefinition.TypeAsString) { -// case "User": -// let siteUrl = listDef.siteUrl; -// let siteUsers = this.getSiteUsers(siteUrl); -// if (siteUsers) { -// switch (siteUsers.status) { -// case SiteUsersStatus.fetched: -// let options: IDropdownOption[] = siteUsers.siteUser.map((opt, index, options) => { -// return { key: opt.id, text: opt.value }; -// }); -// const selectedKey = columnValue ? columnValue.Id : null; -// return ( -// { cellUpdated(selection); } } > -// -// ); -// case SiteUsersStatus.fetching: -// return ( -// -// ); -// case SiteUsersStatus.error: -// return ( -// -// ); -// default: -// return ( -// -// ); -// } -// } else { -// return ( -// -// ); -// } -// case "Lookup": - -// let lookupField = colref.fieldDefinition.LookupField; -// let lookupListId = colref.fieldDefinition.LookupList; -// let lookupWebId = colref.fieldDefinition.LookupWebId; -// /** -// * We are assuming here that the lookup listy is in the same web. -// * -// */ -// lookupWebId = utils.ParseSPField(listDef.webLookup).id; // temp fix. Need to use graph to get the web by id in the site -// let lookupSite = listDef.siteUrl; -// let lookupOptions = this.getLookupOptions(lookupSite, lookupWebId, lookupListId, lookupField); - - -// if (lookupOptions) { -// switch (lookupOptions.status) { -// case LookupOptionStatus.fetched: -// let options: IDropdownOption[] = lookupOptions.lookupOption.map((opt, index, options) => { -// return { key: opt.id, text: opt.value }; -// }); -// return ( -// { cellUpdated(selection); } } > -// -// ); -// case LookupOptionStatus.fetching: -// return ( -// -// ); -// case LookupOptionStatus.error: -// return ( -// -// ); -// default: -// return ( -// -// ); -// } -// } else { -// return ( -// -// ); -// } -// case "Choice": -// const choices = colref.fieldDefinition.Choices.map((c, i) => { - -// let opt: IDropdownOption = { -// index: i, -// key: i, -// text: c, -// isSelected: (c === columnValue) -// }; -// return opt; -// }); -// return ( -// cellUpdated(selection)} > -// -// ); -// case "Text": -// return ( -// ); -// case "Note": -// return ( -// ); - -// case "DateTime": -// const datpickerStrings: IDatePickerStrings = { -// "months": [""], -// "shortMonths": [""], -// "days": [""], -// "shortDays": [""], -// goToToday: "yes" -// }; - -// const year = parseInt(columnValue.substring(0, 34)); -// const month = parseInt(columnValue.substring(5, 7)) - 1; -// const day = parseInt(columnValue.substring(8, 10)); -// const date = new Date(year, month, day); - -// return ( -// ); -// default: -// return ( -// ); -// } -// } - -// /** -// * This method renders the contents of an individual cell in a non-editable format. -// */ -// public CellContents(props: { entity: ListItem, column: SPColumn }): JSX.Element { -// const {entity, column} = props; -// if (!entity.__metadata__ListDefinitionId) { // item is new and list not yet selected -// } -// const listDef = this.getListDefinition(entity.__metadata__ListDefinitionId); -// if (column.type === "__LISTDEFINITIONTITLE__") {// this type is sued to show the listdefinition name -// if (listDef != null) {//listdef has been selected -// return ( -// {listDef.title} -// ); -// } -// else {//listdef not yet selected -// return ( -// Select a list -// ); -// } -// } -// if (!listDef) { // cant edit columns til we select a listdef, not NO onFocus={this.toggleEditing} -// return ( -// select a list first -// -// ); -// } -// const colref = listDef.columnReferences.find(cr => cr.columnDefinitionId === column.key); -// if (colref === undefined) { //Column has not been configured for this list -// return ( -// 'Column Not Defined' -// -// ); -// } -// const internalName = utils.ParseSPField(colref.name).id; - -// switch (colref.fieldDefinition.TypeAsString) { -// case "User": - -// if (entity[internalName] === undefined) { // value not set -// return ( - -// -// ); -// } else { -// return ( -// {entity[internalName]["Title"]} -// -// ); -// } -// case "Lookup": - -// if (entity[internalName] === undefined) { // value not set -// return ( - -// -// ); -// } else { -// return ( -// {entity[internalName][colref.fieldDefinition.LookupField]} -// -// ); -// } -// case "Text": -// return ( -// {entity[internalName]} -// -// ); -// case "Note": -// return ( -// -// ); - -// case "DateTime": -// let value: string; -// if (entity[internalName] === null) { -// return ( - -// ); -// } -// if (colref.fieldDefinition.EntityPropertyName === "DateOnly") { -// value = entity[internalName].split("T")[0]; -// } -// else { -// value = entity[internalName]; -// } -// return ( -// {value} -// -// ); -// default: -// return ( -// {entity[internalName]} -// -// ); -// } -// } -// /** -// * This method renders the A TD for an individual Cell. The TD contains the listItemID and the ColumnID as attributes. -// * It calls CellContentsEditable or CellContents based on whether the cell is being edited. -// * It determines if the cell is being edited by looking at this,props.editing(which got set by ToggleEditing). -// */ -// public TableDetail(props: { entity: ListItem, column: SPColumn, cellUpdated: (newValue) => void, cellUpdatedEvent: (event: React.SyntheticEvent) => void; }): JSX.Element { - -// const {entity, column, cellUpdated, cellUpdatedEvent} = props; -// if (this.state && this.state.editing && this.state.editing.entityid === entity.GUID && this.state.editing.columnid === column.key) { -// return (
-// -//
-// ); -// } else { -// return (
-// -//
-// ); -// } -// } -// public onRenderItemColumn(item: any, index: number, column: SPColumn): any { - -// const listDef = this.getListDefinition(item.__metadata__ListDefinitionId); -// const colRef = listDef.columnReferences.find(cr => cr.columnDefinitionId === column.key); -// const internalName = utils.ParseSPField(colRef.name).id; -// return ( -// -// -// -// ); - - -// } - -// public render() { -// const { listItems } = this.props; -// Log.info("ListItemContainer", "In Render"); - -// const uColumns: SPColumn[] = this.props.columns.map((c, i, a) => { -// return { -// key: c.guid, -// name: c.name, -// fieldName: c.name, -// minWidth: 80, -// maxWidth: 80, - -// width: 80, - -// type: c.type, -// editable: c.editable, - -// }; -// }); - -// return ( - -// -// -// - -// -// ); -// } -// } -// export default connect( -// mapStateToProps, -// mapDispatchToProps -// )(ListItemContainerUIF); diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx new file mode 100644 index 000000000..cef504456 --- /dev/null +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx @@ -0,0 +1,73 @@ +import ColumnDefinition from "../model/ColumnDefinition"; +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { + IPropertyPaneField, + IPropertyPaneFieldType, + IPropertyPaneCustomFieldProps +} from '@microsoft/sp-webpart-base'; +import PropertyFieldColumnDefinitionsHost, { IPropertyFieldColumnDefinitionsHostProps } from './PropertyFieldColumnDefinitionsHost'; + +export interface IPropertyFieldColumnDefinitionsProps { + label: string; + initialValue?: Array; + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + getColumnDefinitions: () =>Array; +} +export interface IPropertyFieldColumnDefinitionsPropsInternal extends IPropertyPaneCustomFieldProps { + label: string; + initialValue?: Array; + targetProperty: string; + onRender(elem: HTMLElement): void; + onDispose(elem: HTMLElement): void; + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + columnDefinitions: Array; +} +class PropertyFieldColumnDefinitionsBuilder implements IPropertyPaneField { + //Properties defined by IPropertyPaneField + public type: IPropertyPaneFieldType = 1;//IPropertyPaneFieldType.Custom; + public targetProperty: string; + public properties: IPropertyFieldColumnDefinitionsPropsInternal; + //Custom properties + private label: string; + private onPropertyChange: (propertyPath: string, oldValue: any, newValue: any) => void; + private customProperties: any; + public constructor(_targetProperty: string, _properties: IPropertyFieldColumnDefinitionsPropsInternal) { + this.render = this.render.bind(this); + this.properties = _properties; + this.label = _properties.label; + this.properties.onDispose = this.dispose; + this.properties.onRender = this.render; + this.onPropertyChange = _properties.onPropertyChange; + this.customProperties = _properties.columnDefinitions; + } + private render(elem: HTMLElement): void { + const element: React.ReactElement = React.createElement(PropertyFieldColumnDefinitionsHost, { + label: this.label, + onPropertyChange: this.onPropertyChange, + columnDefinitions: this.customProperties, + + }); + ReactDom.render(element, elem); + } + private dispose(elem: HTMLElement): void { + } +} +export function PropertyFieldColumnDefinitions(targetProperty: string, properties: IPropertyFieldColumnDefinitionsProps): IPropertyPaneField { + + //Create an internal properties object from the given properties + var newProperties: IPropertyFieldColumnDefinitionsPropsInternal = { + label: properties.label, + targetProperty: targetProperty, + initialValue: properties.initialValue, + onPropertyChange: properties.onPropertyChange, + columnDefinitions: properties.getColumnDefinitions(), + onDispose: null, + onRender: null, + }; + //Calles the PropertyFieldColumnDefinitions builder object + //This object will simulate a PropertyFieldCustom to manage his rendering process + return new PropertyFieldColumnDefinitionsBuilder(targetProperty, newProperties); +} + + diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitionsHost.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitionsHost.tsx new file mode 100644 index 000000000..c4aaaf9d2 --- /dev/null +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitionsHost.tsx @@ -0,0 +1,110 @@ +import { Guid } from "@microsoft/sp-client-base"; +import { ColumnDefinitionContainerNative } from "./ColumnDefinitionContainer"; +import ColumnDefinition from "../model/ColumnDefinition"; +import * as React from 'react'; +import { Label } from 'office-ui-fabric-react/lib/Label'; +import { Button } from 'office-ui-fabric-react/lib/Button'; +import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; +import * as strings from "spfxReactGridStrings"; +export interface IPropertyFieldColumnDefinitionsHostProps { + label: string; + initialValue?: Array; + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + columnDefinitions: Array; +} +export interface IPropertyFieldColumnDefinitionsHostState { + openPanel?: boolean; + columnDefinitions: Array; +} +export default class PropertyFieldColumnDefinitionsHost extends React.Component { + + constructor(props: IPropertyFieldColumnDefinitionsHostProps) { + + super(props); + this.onOpenPanel = this.onOpenPanel.bind(this); + this.onClosePanel = this.onClosePanel.bind(this); + this.removeColumn = this.removeColumn.bind(this); + this.addColumn = this.addColumn.bind(this); + this.moveColumnDown = this.moveColumnDown.bind(this); + this.moveColumnUp = this.moveColumnUp.bind(this); + this.saveChanges = this.saveChanges.bind(this); + this.state = { + columnDefinitions: this.props.columnDefinitions, + openPanel: false + }; + } + private addColumn(): void { + const id = Guid.newGuid(); + const col: ColumnDefinition = new ColumnDefinition(id.toString(), "", 80, true); + this.state.columnDefinitions.push(col); + this.setState(this.state); + } + private removeColumn(column): void { + this.state.columnDefinitions = _.filter(this.state.columnDefinitions, (o) => { return o.guid !== column.guid; }); + this.setState(this.state); + } + private removeAllColumns(): void { + this.state.columnDefinitions = []; + this.setState(this.state); + + } + private moveColumnUp(column: ColumnDefinition): void { + + const index = _.findIndex(this.state.columnDefinitions, (cd) => cd.guid === column.guid); + this.state.columnDefinitions[index] = this.state.columnDefinitions.splice(index - 1, 1, this.state.columnDefinitions[index])[0]; + this.setState(this.state); + } + private moveColumnDown(column): void { + + const index = _.findIndex(this.state.columnDefinitions, (cd) => cd.guid === column.guid); + this.state.columnDefinitions[index] = this.state.columnDefinitions.splice(index + 1, 1, this.state.columnDefinitions[index])[0]; + this.setState(this.state); + + } + private saveChanges(): void { + if (this.props.onPropertyChange) { + this.props.onPropertyChange("ColumnDefinitions", this.props.initialValue, this.state.columnDefinitions); + this.onClosePanel(); + } + } + private onOpenPanel(element?: any): void { + + this.state.openPanel = true; + this.setState(this.state); + } + private onClosePanel(element?: any): void { + this.state.openPanel = false; + this.setState(this.state); + } + public render(): JSX.Element { + + //Renders content + return ( +
+ + + {this.state.openPanel === true ? + + + + + : ''} + +
+ ); + } +} + + + diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx new file mode 100644 index 000000000..b0025a2c9 --- /dev/null +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx @@ -0,0 +1,87 @@ +import ListDefinition from "../model/ListDefinition"; +import ColumnDefinition from "../model/ColumnDefinition"; +import * as React from 'react'; +import * as ReactDom from 'react-dom'; +import { + IPropertyPaneField, + IPropertyPaneFieldType, + IPropertyPaneCustomFieldProps +} from '@microsoft/sp-webpart-base'; +import PropertyFieldListDefinitionsHost, { IPropertyFieldListDefinitionsHostProps } from './PropertyFieldListDefinitionsHost'; +import { PageContext } from "@microsoft/sp-client-base"; +export interface IPropertyFieldListDefinitionsProps { + label: string; + initialValue?: Array; + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + getListDefinitions: () => Array; + getColumnDefinitions: () =>Array; + PageContext: PageContext; +} +export interface IPropertyFieldListDefinitionsPropsInternal extends IPropertyPaneCustomFieldProps { + label: string; + initialValue?: Array; + targetProperty: string; + onRender(elem: HTMLElement): void; + onDispose(elem: HTMLElement): void; + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + getListDefinitions: () => Array; + getColumnDefinitions: () =>Array; + PageContext: PageContext; +} +class PropertyFieldListDefinitionsBuilder implements IPropertyPaneField { + + //Properties defined by IPropertyPaneField + public type: IPropertyPaneFieldType = 1;//IPropertyPaneFieldType.Custom; + public targetProperty: string; + public properties: IPropertyFieldListDefinitionsPropsInternal; + + //Custom properties + private label: string; + private onPropertyChange: (propertyPath: string, oldValue: any, newValue: any) => void; + private customProperties: any; + public constructor(_targetProperty: string, _properties: IPropertyFieldListDefinitionsPropsInternal) { + + this.render = this.render.bind(this); + this.properties = _properties; + this.label = _properties.label; + this.properties.onDispose = this.dispose; + this.properties.onRender = this.render; + this.onPropertyChange = _properties.onPropertyChange; + } + private render(elem: HTMLElement): void { + + const ldProps: IPropertyFieldListDefinitionsHostProps = { + label: this.label, + getColumnDefinitions: this.properties.getColumnDefinitions, + getListDefinitions: this.properties.getListDefinitions, + onPropertyChange: this.onPropertyChange, + PageContext: this.properties.PageContext + + }; + + const element: React.ReactElement = React.createElement(PropertyFieldListDefinitionsHost, ldProps); + ReactDom.render(element, elem); + } + private dispose(elem: HTMLElement): void { + } +} +export function PropertyFieldListDefinitions(targetProperty: string, properties: IPropertyFieldListDefinitionsProps): IPropertyPaneField { + + //Create an internal properties object from the given properties + var newProperties: IPropertyFieldListDefinitionsPropsInternal = { + label: properties.label, + targetProperty: targetProperty, + initialValue: properties.initialValue, + onPropertyChange: properties.onPropertyChange, + getListDefinitions: properties.getListDefinitions, + getColumnDefinitions: properties.getColumnDefinitions, + PageContext:properties.PageContext, + onDispose: null, + onRender: null, + }; + //Calles the PropertyFieldListDefinitions builder object + //This object will simulate a PropertyFieldCustom to manage his rendering process + return new PropertyFieldListDefinitionsBuilder(targetProperty, newProperties); +} + + diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitionsHost.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitionsHost.tsx new file mode 100644 index 000000000..9cc06fff8 --- /dev/null +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitionsHost.tsx @@ -0,0 +1,205 @@ + +import { ListDefinitionContainerNative } from "./ListDefinitionContainer"; +import ListDefinition from "../model/ListDefinition"; +import ColumnDefinition from "../model/ColumnDefinition"; +import { Site, Web, WebList, WebListField } from "../model/Site"; +import * as React from 'react'; +import { Label } from 'office-ui-fabric-react/lib/Label'; +import { Button } from 'office-ui-fabric-react/lib/Button'; +import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel'; +import * as strings from "spfxReactGridStrings"; +import * as utils from "../utils/utils"; +import { Web as SPWeb } from "sp-pnp-js"; +import { Site as SPSite } from "sp-pnp-js"; +import { Guid, PageContext } from "@microsoft/sp-client-base"; + +export interface IPropertyFieldListDefinitionsHostProps { + label: string; + initialValue?: Array; + onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; + getListDefinitions: () => Array; + getColumnDefinitions: () => Array; + PageContext: PageContext; +} +export interface IPropertyFieldListDefinitionsHostState { + openPanel?: boolean; + ListDefinitions: Array; + Sites: Array; +} +export default class PropertyFieldListDefinitionsHost extends React.Component { + + constructor(props: IPropertyFieldListDefinitionsHostProps) { + + super(props); + this.onOpenPanel = this.onOpenPanel.bind(this); + this.onClosePanel = this.onClosePanel.bind(this); + this.removeList = this.removeList.bind(this); + this.addList = this.addList.bind(this); + this.moveListUp = this.moveListUp.bind(this); + this.moveListDown = this.moveListDown.bind(this); + this.getWebs = this.getWebs.bind(this); + this.getListsForWeb = this.getListsForWeb.bind(this); + this.getFieldsForList = this.getFieldsForList.bind(this); + this.saveChanges = this.saveChanges.bind(this); + this.state = { + ListDefinitions: this.props.getListDefinitions(), + openPanel: false, + Sites: [] + }; + } + private getWebs(siteUrl: string): any { + + const spSite: SPSite = new SPSite(siteUrl); + const promise = spSite.rootWeb.webs.orderBy("Title").get() + .then((response) => { + const data = _.map(response, (item: any) => { + const web: Web = new Web(item.Id, item.Title, item.Url); + return web; + }); + console.log(data); + let site: Site = new Site(siteUrl); + site.webs = data; + this.state.Sites.push(site); + this.setState(this.state); + }) + .catch((error) => { + console.log(error); + }); + return promise; + } + + private getListsForWeb(webUrl: string): any { + + const spWeb = new SPWeb(webUrl); + const promise = spWeb.lists.orderBy("Title").get() + .then((response) => { + const data = _.map(response, (item: any) => { + return new WebList(item.Id, item.Title, item.Url, ); + }); + for (const site of this.state.Sites) { + for (const web of site.webs) { + if (web.url === webUrl) { + web.lists = data; + web.listsFetched = true; + } + } + } + this.setState(this.state); + + }) + .catch((error) => { + console.log(error); + }); + return promise; + } + + private getFieldsForList(webUrl: string, listId: string): any { + + const spWeb = new SPWeb(webUrl); + const promise = spWeb.lists.getById(listId).fields.filter("Hidden eq false").orderBy("Title").get() + .then((response) => { + const data = _.map(response, (item: any) => { + return new WebListField(item.id, new utils.ParsedSPField(item.InternalName, item.Title).toString(), item); + }); + for (const site of this.state.Sites) { + for (const web of site.webs) { + if (web.url === webUrl) { + for (const list of web.lists) { + if (list.id === listId) { + list.fields = data; + list.fieldsFetched = true; + } + } + } + } + } + this.setState(this.state); + + }) + .catch((error) => { + console.log(error); + }); + return promise; + } + private addList(siteUrl): void { + const id = Guid.newGuid(); + const list: ListDefinition = new ListDefinition(id.toString(), null, null, siteUrl, null, null); + + this.state.ListDefinitions.push(list); + this.setState(this.state); + } + private removeList(list: ListDefinition): void { + + this.state.ListDefinitions = _.filter(this.state.ListDefinitions, (o) => { return o.guid !== list.guid; }); + this.setState(this.state); + } + private removeAllLists(): void { + + this.state.ListDefinitions = []; + this.setState(this.state); + } + private moveListUp(list: ListDefinition): void { + + const index = _.findIndex(this.state.ListDefinitions, (cd) => cd.guid === list.guid); + this.state.ListDefinitions[index] = this.state.ListDefinitions.splice(index - 1, 1, this.state.ListDefinitions[index])[0]; + this.setState(this.state); + } + private moveListDown(list: ListDefinition): void { + + const index = _.findIndex(this.state.ListDefinitions, (cd) => cd.guid === list.guid); + this.state.ListDefinitions[index] = this.state.ListDefinitions.splice(index + 1, 1, this.state.ListDefinitions[index])[0]; + this.setState(this.state); + + } + private saveChanges(): void { + if (this.props.onPropertyChange) { + this.props.onPropertyChange("ListDefinitions", this.props.initialValue, this.state.ListDefinitions); + this.onClosePanel(); + } + } + private onOpenPanel(element?: any): void { + + this.state.openPanel = true; + this.setState(this.state); + } + private onClosePanel(element?: any): void { + this.state.openPanel = false; + this.setState(this.state); + } + public render(): JSX.Element { + + //Renders content + return ( +
+ + + {this.state.openPanel === true ? + + + + + : ''} + +
+ ); + } +} + + + diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/app.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/app.tsx index c5623766b..8721d92cf 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/app.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/app.tsx @@ -1,12 +1,10 @@ import * as React from "react"; import { - Button, MessageBar, MessageBarType } from "office-ui-fabric-react"; const connect = require("react-redux").connect; import SystemStatus from "../model/SystemStatus"; -const Link = require("react-router").Link; import Content from "../components/content"; interface IAppProps extends React.Props { systemStatus: SystemStatus; @@ -35,9 +33,7 @@ class App extends React.Component { const { children} = this.props; return (
- - - +
{this.messageBar(this.props.systemStatus.fetchStatus)}
{this.props.systemStatus.currentAction} diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/en-us.js b/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/en-us.js index 89f98bc1e..69ce3b1cb 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/en-us.js +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/en-us.js @@ -1,7 +1,18 @@ -define([], function() { +define([], function () { return { "PropertyPaneDescription": "Description", "BasicGroupName": "Group Name", - "DescriptionFieldLabel": "Description Field" + "DescriptionFieldLabel": "Description Field", + + "ColumnDefinitionFieldLabel": "Column Definitions", + "ColumnDefinitionsButtonSelect": "Update Columns", + "ColumnDefinitionsButtonReset": "Clear", + "ColumnDefinitionsTitle": "Column Definitions", + + "ListDefinitionFieldLabel": "List Definitions", + "ListDefinitionsButtonSelect": "Update Lists", + "ListDefinitionsButtonReset": "Clear", + "ListDefinitionsTitle": "List Definitions" + } }); \ No newline at end of file diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/mystrings.d.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/mystrings.d.ts index f66dd2a1e..dcd4879a0 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/mystrings.d.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/loc/mystrings.d.ts @@ -2,8 +2,22 @@ declare interface ISpfxReactGridStrings { PropertyPaneDescription: string; BasicGroupName: string; DescriptionFieldLabel: string; -} + ListDefinitionFieldLabel:string; + /**The Label on the reset CD Button */ + ListDefinitionsButtonSelect:string; + /**The Label on the reset Button */ + ListDefinitionsButtonReset:string; + /**The Title on the popupPage */ + ListDefinitionsTitle:string; + ColumnDefinitionFieldLabel:string; + /**The Label on the reset CD Button */ + ColumnDefinitionsButtonSelect:string; + /**The Label on the reset Button */ + ColumnDefinitionsButtonReset:string; + /**The Title on the popupPage */ + ColumnDefinitionsTitle:string; +} declare module 'spfxReactGridStrings' { const strings: ISpfxReactGridStrings; export = strings; diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ColumnReducer.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ColumnReducer.ts index 317080933..337be7104 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ColumnReducer.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ColumnReducer.ts @@ -9,14 +9,14 @@ import { MOVE_COLUMN_DOWN } from "../constants"; -function moveColumnUp(state: Array, action) { +export function moveColumnUp(state: Array, action) { let newstate = _.clone(state); const index = _.findIndex(newstate, c => c.guid === action.payload.column.guid); newstate[index] = newstate.splice(index - 1, 1, newstate[index])[0]; return newstate; } -function moveColumnDown(state: Array, action) { +export function moveColumnDown(state: Array, action) { let newstate = _.clone(state); let index = _.findIndex(newstate, c => c.guid === action.payload.column.guid);