Added Web Selector to let user drill down to web (#124)

* rc0

* rc0

* more rc0

* rc0

* RC0 Done

* cleanup manifest

* Working on Web Selecor

* qqqqqqq

* 111

* Added Web selector to drill down to web

* fix merge conflicts

* s

* renmove react-router

* added saveall and undoall

* Add support for ID field

* saveAll

* fixed saveall across sites

* mark items as deleted (soft delete)

* work on soft delete

* work on soft delete

* Enable soft deletes

* Add SortInfo to columnDefinitions

* Work on initial sort

* pass columnDefinitions into listitemreducer for sort

* trying to get sortablecolumns in reducer...

* setup sort sequences

* work on compareres

* work on sort

* basic sorting workoing

* fix linting errors
This commit is contained in:
Russell gove 2017-02-10 19:27:24 -05:00 committed by Vesa Juvonen
parent dd51452dd9
commit f137105fb4
26 changed files with 1113 additions and 197 deletions

View File

@ -15,8 +15,6 @@
"react-data-grid": "^1.0.62",
"react-dom": "0.14.8",
"react-redux": "^4.4.6",
"react-router": "^2.8.1",
"react-router-redux": "^4.0.5",
"redux": "^3.4.0",
"redux-localstorage": "^0.4.1",
"redux-logger": "^2.6.0",

View File

@ -2,9 +2,8 @@ import * as React from "react";
import * as ReactDom from "react-dom";
import { Provider } from "react-redux";
import configureStore from "./store/configure-store";
//const { Router, createMemoryHistory } = require("react-router");
import { Router, createMemoryHistory } from "react-router";
import * as Redux from "redux";
import AppContainer from "./containers/App";
import { addColumns, removeAllColumns } from "./actions/columnActions";
import { addLists, removeAllLists } from "./actions/listActions";
import { PropertyFieldColumnDefinitions, IPropertyFieldColumnDefinitionsProps } from "./containers/PropertyFieldColumnDefinitions";
@ -12,21 +11,18 @@ import { PropertyFieldListDefinitions, IPropertyFieldListDefinitionsProps } from
import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
IWebPartContext,
PropertyPaneTextField
} from "@microsoft/sp-webpart-base";
debugger;
import { Log } from "@microsoft/sp-core-library";
import routes from "./store/routes";
import * as strings from "spfxReactGridStrings";
import { ISpfxReactGridWebPartProps } from "./ISpfxReactGridWebPartProps";
const store: Redux.Store<any> = configureStore({});
const history = createMemoryHistory(location);
const App: React.StatelessComponent<any> = () => (
<Provider store={store}>
<Router history={history}>
{routes}
</Router>
<AppContainer />
</Provider>
);
export default class SpfxReactGridWebPart extends BaseClientSideWebPart<ISpfxReactGridWebPartProps> {
@ -34,7 +30,7 @@ export default class SpfxReactGridWebPart extends BaseClientSideWebPart<ISpfxRea
private cdProps: IPropertyFieldColumnDefinitionsProps;
private ldProps: IPropertyFieldListDefinitionsProps;
public constructor() {
debugger;
super();
this.onPropertyChange = this.onPropertyChange.bind(this);
this.cdProps = {

View File

@ -26,8 +26,9 @@ import * as _ from "lodash";
import { Web, TypedHash } from "sp-pnp-js";
import ListItem from "../model/ListItem";
import GridRowStatus from "../Model/GridRowStatus";
import { Log } from "@microsoft/sp-core-library";
import ListDefinition from "../model/ListDefinition";
import ColumnDefinition from "../model/ColumnDefinition";
export function clearListItems() {
return {
type: CLEAR_LISTITEMS,
@ -48,8 +49,7 @@ export function removeListItem(dispatch: any, listItem: ListItem, listDefinition
const listid = utils.ParseSPField(listDefinition.listLookup).id;
const web = new Web(weburl);
switch (listItem.__metadata__GridRowStatus) {
case GridRowStatus.modified:
case GridRowStatus.pristine:
case GridRowStatus.toBeDeleted:
web.lists.getById(listid).items.getById(listItem.ID).recycle()
.then((response) => {
// shouwld have an option to rfresh here in cas of calculated columns
@ -67,6 +67,8 @@ export function removeListItem(dispatch: any, listItem: ListItem, listDefinition
listItem: listItem
}
};
default:
Log.warn("ListItemContainer", "Invalid GrodrowStatus in update ListiteRender-- " + listItem.__metadata__GridRowStatus.toString());
}
}
export function removeListItemSuccessAction(listItem) {
@ -112,7 +114,7 @@ export function listDefinitionIsValid(listDefinition: ListDefinition): boolean {
* Action to update a listitem in sharepoint
*/
export function updateListItemAction(dispatch: any, listDefinition: ListDefinition, listItem: ListItem): any {
// listDefinition = this.getListDefinition(listItem.__metadata__ListDefinitionId);// The list Definition this item is associated with.
const weburl = utils.ParseSPField(listDefinition.webLookup).id;
const listid = utils.ParseSPField(listDefinition.listLookup).id;
const web = new Web(weburl);
@ -120,6 +122,9 @@ export function updateListItemAction(dispatch: any, listDefinition: ListDefiniti
for (const columnRef of listDefinition.columnReferences) {
let fieldName = utils.ParseSPField(columnRef.name).id;
switch (columnRef.fieldDefinition.TypeAsString) {
case "Counter": // do not send ID to shareppoint as a data field
break;
case "Lookup":
if (listItem[fieldName]) {// field may not be set
typedHash[fieldName + "Id"] = listItem[fieldName].Id;
@ -280,10 +285,9 @@ export function gotListItemAction(item) {
}
};
}
export function getListItemsAction(dispatch: any, listDefinitions: Array<ListDefinition>): any {
export function getListItemsAction(dispatch: any, listDefinitions: Array<ListDefinition>, columnDefinitions: Array<ColumnDefinition>): any {
dispatch(clearListItems());
const promises: Array<Promise<any>> = new Array<Promise<any>>();
const promises: Array<Promise<any>> = new Array<Promise<any>>();
for (const listDefinition of listDefinitions) {
if (!listDefinitionIsValid(listDefinition)) {
break;
@ -326,7 +330,7 @@ export function getListItemsAction(dispatch: any, listDefinitions: Array<ListDef
return item;
});
console.log(data);
const gotListItems = gotListItemsAction(data);
const gotListItems = gotListItemsAction(data,listDefinitions,columnDefinitions);
dispatch(gotListItems); // need to ewname this one to be digfferent from the omported ome
})
.catch((error) => {
@ -343,6 +347,7 @@ export function getListItemsAction(dispatch: any, listDefinitions: Array<ListDef
promise: Promise.all(promises)
}
};
return action;
}
export function getListItemsErrorAction(error) {
@ -354,11 +359,13 @@ export function getListItemsErrorAction(error) {
};
}
export function gotListItemsAction(items) {
export function gotListItemsAction(items: Array<ListItem>, listDefinitions: Array<ListDefinition>, columnDefinitions: Array<ColumnDefinition>) {
return {
type: GOT_LISTITEMS,
payload: {
items: items
items: items,
listDefinitions: listDefinitions,
columnDefinitions: columnDefinitions
}
};
}

View File

@ -1,39 +0,0 @@
import * as React from "react";
import { Web } from "../model/Site";
export interface KeyValue {
value: any;
displayName: string;
}
import { Dropdown, IDropdownOption } from "office-ui-fabric-react";
export interface IWebEditorProps extends React.Props<any> {
selectedValue?: string;
onChange(event): void;
webs: Array<Web>;
}
export default class WebEditor extends React.Component<IWebEditorProps, void> {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
}
private handleChange(selectedItem:IDropdownOption) {
this.props.onChange(selectedItem.key);
}
public render() {
const { selectedValue, webs} = this.props;
let options: Array<IDropdownOption> = webs.map((web) => {
return ({
key: web.url + "#;" + web.title,
text: web.title
});
});
options.unshift({ key: null, text: "..Select One" });
return (
<Dropdown label="" selectedKey={selectedValue} options={options} onChanged={this.handleChange} >
</Dropdown >
);
}
}

View File

@ -0,0 +1,128 @@
import { Web } from "../model/Site";
import * as _ from "underscore";
import * as React from 'react';
import { Label } from 'office-ui-fabric-react/lib/Label';
import { Button, ButtonType } from 'office-ui-fabric-react/lib/Button';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/Dropdown';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/Panel';
import * as utils from "../utils/utils";
import { Web as SPWeb } from "sp-pnp-js";
import { PageContext } from "@microsoft/sp-page-context";
export interface IWebSelectorProps {
onChange(newValue: any): void;
PageContext: PageContext;
selectedWeb: string;
siteUrl: string;
headerText: string;
}
export interface IWebSelectorState {
openPanel: boolean;
selectedWeb: string;
options: Array<IDropdownOption>;
}
export default class WebSelector extends React.Component<IWebSelectorProps, IWebSelectorState> {
constructor(props: IWebSelectorProps) {
super(props);
this.onOpenPanel = this.onOpenPanel.bind(this);
this.onClosePanel = this.onClosePanel.bind(this);
this.getWebs = this.getWebs.bind(this);
this.SelectedWebChanged = this.SelectedWebChanged.bind(this);
this.state = {
openPanel: false,
selectedWeb: this.props.selectedWeb,
options: []
};
}
private getWebs(webUrl: string): any {
const spWeb: SPWeb = new SPWeb(webUrl);
const promise = spWeb.webs.orderBy("Title").get()
.then((response) => {
const webs = _.map(response, (item: any) => {
const web: Web = new Web(item.Id, item.Title, item.Url);
return web;
});
console.log(webs);
return webs;
})
.catch((error) => {
console.log(error);
});
return promise;
}
private onOpenPanel(element?: any): void {
this.state.openPanel = true;
this.setState(this.state);
this.getWebs(this.props.siteUrl).then(webs => {
let options: Array<IDropdownOption> = webs.map((web) => {
return ({
key: web.url + "#;" + web.title,
text: web.title
});
});
options.unshift({ key: "", text: "Choose a web to select it" });
this.state.options = options;
this.setState(this.state);
});
}
private SelectedWebChanged(option: IDropdownOption, index?: number): void {
const key: string = option.key as string;
if (key === "") { return; }
const webUrl = utils.ParseSPField(key).id;
this.state.selectedWeb = key;
this.props.onChange(key);
this.getWebs(webUrl).then(webs => {
let options: Array<IDropdownOption> = webs.map((web) => {
return ({
key: web.url + "#;" + web.title,
text: web.title
});
});
options.unshift({ key: "", text: "Select one...", selected: true });
this.state.options = options;
this.setState(this.state);
});
}
private onClosePanel(element?: any): void {
this.state.openPanel = false;
this.setState(this.state);
}
public render(): JSX.Element {
return (
<div style={{ marginBottom: '8px' }}>
{this.state.openPanel === true ?
<Panel
isOpen={this.state.openPanel} hasCloseButton={true} onDismiss={this.onClosePanel}
isLightDismiss={true} type={PanelType.smallFixedFar}
headerText={this.props.headerText}>
<div>
<span> <Label>Site Url</Label> {this.props.siteUrl}</span>
</div>
<div>
<Label>Currently selected Web Url</Label> {utils.ParseSPField(this.state.selectedWeb).id}
</div>
<div>
<Label>Currently selected Web Title</Label> {utils.ParseSPField(this.state.selectedWeb).value}
</div>
<Dropdown label="Choose a different Web"
options={this.state.options}
onChanged={this.SelectedWebChanged} defaultSelectedKey="">
</Dropdown>
</Panel>
: <div>
{utils.ParseSPField(this.state.selectedWeb).value}
<Button buttonType={ButtonType.icon} icon="Search" onClick={this.onOpenPanel}></Button>
</div>
}
</div>
);
}
}

View File

@ -1,4 +1,4 @@
import * as React from 'react';
/*import * as React from 'react';
export interface IContentProps extends React.Props<any> {
isVisible: boolean;
@ -11,4 +11,4 @@ export default function Content({ children = null, isVisible }: IContentProps) {
{ isVisible ? children : null }
</main>
);
}
}*/

View File

@ -1,11 +1,12 @@
import * as React from "react";
import { SharePointLookupCellFormatter } from "../components/SharePointFormatters";
//const connect = require("react-redux").connect;
import {connect} from "react-redux";
import { connect } from "react-redux";
import * as _ from "lodash";
import { addColumn, removeColumn, removeAllColumns, moveCulumnUp, moveCulumnDown } from "../actions/columnActions";
import { addColumn, removeColumn, removeAllColumns, moveCulumnUp, moveCulumnDown } from "../actions/columnActions";
import ColumnDefinition from "../model/ColumnDefinition";
import { Button, ButtonType, TextField, CommandBar, Dropdown, IDropdownOption, Toggle } from "office-ui-fabric-react";
import { SortDirection } from "../model/ColumnDefinition";
import { Button, ButtonType, TextField, CommandBar, Dropdown, IDropdownOption, Toggle, Slider } from "office-ui-fabric-react";
import Container from "../components/container";
import { Guid, Log } from "@microsoft/sp-core-library";
/** NOTE:
@ -29,6 +30,7 @@ const fieldTypes: Array<IDropdownOption> = [
// { key: "Counter", text: "Counter" },
{ key: "Choice", text: "Choice" },
{ key: "Lookup", text: "Lookup" },
{ key: "Counter", text: "Coumter (Item ID)" },
// { key: "Boolean", value: "Boolean" },
{ key: "Number", text: "Number" },
// { key: "Currency", value: "Currency" },
@ -47,6 +49,13 @@ const fieldTypes: Array<IDropdownOption> = [
// { name: "WorkflowStatus", value: "WorkflowStatus" },
// { name: "WorkflowEventType", value: "WorkflowEventType" },
];
const sortDirectionOptions: Array<IDropdownOption> = [
{ key: SortDirection.None, text: SortDirection[SortDirection.None] },
{ key: SortDirection.Ascending, text: SortDirection[SortDirection.Ascending] },
{ key: SortDirection.Descending, text: SortDirection[SortDirection.Descending] },
];
export interface IColumnsPageProps extends React.Props<any> {
columns: Array<ColumnDefinition>;
@ -59,7 +68,7 @@ export interface IColumnsPageProps extends React.Props<any> {
save: () => void;
}
interface IContextMenu extends React.Props<any> {
// onRowDelete: AdazzleReactDataGrid.ColumnEventCallback;
// onRowDelete: AdazzleReactDataGrid.ColumnEventCallback;
}
function mapStateToProps(state) {
return {
@ -70,7 +79,7 @@ function mapDispatchToProps(dispatch) {
return {
addColumn: (): void => {
const id = Guid.newGuid();
const col: ColumnDefinition = new ColumnDefinition(id.toString(), "", 80, true);
const col: ColumnDefinition = new ColumnDefinition(id.toString(), "", 80, true, );
dispatch(addColumn(col));
},
@ -151,6 +160,24 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
editor: "BooleanEditor",
formatter: "BooleanFormatter",
width: 99
},
{
id: "sortSequence",
name: "sortSequence",
editable: true,
width: 99,
editor: "SortSequenceEditor",
},
{
id: "sortDirection",
name: "sortDirection",
editable: true,
width: 99,
editor: "SortDirectionEditor",
formatter: "SortDirectionFormatter",
}];
private handleCellUpdatedEvent(event) { //native react uses a Synthetic event
@ -158,8 +185,8 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
}
private handleCellUpdated(value) { // Office UI Fabric does not use events. It just calls this method with the new value
let {entityid, columnid} = this.state.editing;
const entity: ColumnDefinition =_.find( this.props.columns,(temp) => temp.guid === entityid);
const column = _.find(this.gridColulumns,(temp )=> temp.id === columnid);
const entity: ColumnDefinition = _.find(this.props.columns, (temp) => temp.guid === entityid);
const column = _.find(this.gridColulumns, (temp) => temp.id === columnid);
entity[column.name] = value;
// this.props.saveColumn(entity);
@ -171,7 +198,7 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
const target = this.getParent(event.target, "TD");
const attributes: NamedNodeMap = target.attributes;
const entityId = attributes.getNamedItem("data-entityid").value;
const column: ColumnDefinition =_.find( this.props.columns,cd => cd.guid === entityId);
const column: ColumnDefinition = _.find(this.props.columns, cd => cd.guid === entityId);
this.props.moveColumnUp(column);
return;
}
@ -181,7 +208,7 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
const target = this.getParent(event.target, "TD");
const attributes: NamedNodeMap = target.attributes;
const entityId = attributes.getNamedItem("data-entityid").value;
const column: ColumnDefinition = _.find(this.props.columns,cd => cd.guid === entityId);
const column: ColumnDefinition = _.find(this.props.columns, cd => cd.guid === entityId);
this.props.moveColumnDown(column);
return;
}
@ -191,7 +218,7 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
const target = this.getParent(event.target, "TD");
const attributes: NamedNodeMap = target.attributes;
const entityId = attributes.getNamedItem("data-entityid").value;
const column: ColumnDefinition = _.find(this.props.columns,cd => cd.guid === entityId);
const column: ColumnDefinition = _.find(this.props.columns, cd => cd.guid === entityId);
this.props.removeColumn(column);
return;
}
@ -227,12 +254,30 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
<Dropdown label="" selectedKey={entity[gridColumn.name]} options={fieldTypes} onChanged={(selection: IDropdownOption) => cellUpdated(selection.key)} >
</Dropdown >
);
case "SortSequenceEditor":
return (
<Slider
onChange={selection => cellUpdated(selection)}
min={1}
max={10}
value={entity[gridColumn.name]}
>
</Slider >
);
case "SortDirectionEditor":
return (
<Dropdown label="" selectedKey={entity[gridColumn.name]}
options={sortDirectionOptions}
onChanged={(selection: IDropdownOption) => cellUpdated(selection.key)}
>
</Dropdown >
);
default:
return (
<TextField autoFocus width={gridColumn.width}
value={entity[gridColumn.name]}
onChanged={cellUpdated} // this does not use eventing. It just calls the method. onChanged NOT onChange
/>);
/>);
}
}
public CellContents(props: { entity: ColumnDefinition, gridColumn: GridColumn }): JSX.Element {
@ -253,6 +298,13 @@ export class ColumnDefinitionContainerNative extends React.Component<IColumnsPag
// );
let result = (entity[gridColumn.name]) ? (<div>Yes</div>) : (<div>No</div>);
return result;
case "SortDirectionFormatter":
return (
<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }}>
{SortDirection[entity[gridColumn.name]]}
</a>
);
case "SharePointLookupCellFormatter":
return (<SharePointLookupCellFormatter value={entity[gridColumn.name]} onFocus={this.toggleEditing} />);
default:

View File

@ -1,10 +1,10 @@
import * as React from "react";
import * as utils from "../utils/utils";
//const connect = require("react-redux").connect;
import * as strings from "spfxReactGridStrings";
import {connect} from "react-redux";
import * as _ from "lodash";
import { SharePointLookupCellFormatter } from "../components/SharePointFormatters";
import WebEditor from "../components/WebEditor";
import WebSelector from "../components/WebSelector";
import ListEditor from "../components/ListEditor";
import { addList, removeList, saveList, removeAllLists } from "../actions/listActions";
import { getWebsAction, getListsForWebAction, getFieldsForListAction } from "../actions/SiteActions";
@ -326,9 +326,15 @@ export class ListDefinitionContainerNative extends React.Component<IListViewPage
switch (column.editor) {
case "WebEditor":
let webs = this.getWebsForSite(entity);
return (<WebEditor webs={webs} selectedValue={columnValue} onChange={cellUpdated} />);
return (
<WebSelector
selectedWeb={columnValue}
onChange={cellUpdated}
PageContext={this.props.pageContext}
siteUrl={entity.siteUrl}
headerText={strings.WebSelectorHeaderText}
/>
);
case "ListEditor":
let lists = this.getListsForWeb(entity);// the Id portion of the WebLookup is the URL
return (<ListEditor selectedValue={columnValue} onChange={cellUpdated} lists={lists} />);

View File

@ -2,7 +2,7 @@
import * as React from "react";
//const connect = require("react-redux").connect;
import {connect} from "react-redux";
import { connect } from "react-redux";
import * as _ from "lodash";
import {
addListItem, removeListItem, getListItemsAction, saveListItemAction,
@ -40,7 +40,7 @@ interface IListViewPageProps extends React.Props<any> {
/** 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<ListDefinition>) => void;
getListItems: (listDefinitions: Array<ListDefinition>, columnDefinitions: Array<ColumnDefinition>) => void;
/** Redux Action to update a listitem in sharepoint */
updateListItem: (ListItem: ListItem, ListDef: ListDefinition) => Promise<any>;
/** Redux Action to get the lookup options for a specific field */
@ -53,7 +53,6 @@ interface IListViewPageProps extends React.Props<any> {
saveListItem: (ListItem) => void;
}
function mapStateToProps(state) {
return {
listItems: state.items,
columns: state.columns,
@ -93,8 +92,8 @@ function mapDispatchToProps(dispatch) {
dispatch(undoListItemChangesAction(listItem));
},
getListItems: (listDefinitions: Array<ListDefinition>): void => {
dispatch(getListItemsAction(dispatch, listDefinitions));
getListItems: (listDefinitions: Array<ListDefinition>, columnDefinitions: Array<ColumnDefinition>): void => {
dispatch(getListItemsAction(dispatch, listDefinitions, columnDefinitions));// Column Defs needed to sort
},
getLookupOptionAction: (lookupSite, lookupWebId, lookupListId, lookupField): void => {
dispatch(getLookupOptionAction(dispatch, lookupSite, lookupWebId, lookupListId, lookupField));
@ -131,16 +130,36 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
this.TableRows = this.TableRows.bind(this);
this.toggleEditing = this.toggleEditing.bind(this);
this.addListItem = this.addListItem.bind(this);
this.removeListItem = this.removeListItem.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.HandleUndoItemChangesEvent = this.HandleUndoItemChangesEvent.bind(this);
this.handleUpdateListItemEvent = this.handleUpdateListItemEvent.bind(this);
this.updateListItem = this.updateListItem.bind(this);
this.getLookupOptions = this.getLookupOptions.bind(this);
this.saveAll = this.saveAll.bind(this);
this.undoAll = this.undoAll.bind(this);
this.markListItemAsDeleted = this.markListItemAsDeleted.bind(this);
}
private addListItem(): void {
private saveAll(): void {
const unsavedItems = _.filter(this.props.listItems, item => {
return item.__metadata__OriginalValues;
});
for (const entity of unsavedItems) {
this.updateListItem(entity);
}
}
private undoAll(): void {
const unsavedItems = _.filter(this.props.listItems, item => {
return item.__metadata__OriginalValues;
});
for (const unsavedItem of unsavedItems) {
this.props.undoItemChanges(unsavedItem);
}
}
private addListItem(): void {
let listItem = new ListItem();
for (const column of this.props.columns) {
listItem[column.name] = null;
@ -153,29 +172,28 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
this.props.addListItem(listItem);
}
private removeListItem(event): void {
// private removeListItem(event): void {
const parentTD = this.getParent(event.target, "TD");
const attributes: NamedNodeMap = parentTD.attributes;
const entityid = attributes.getNamedItem("data-entityid").value; // theid of the SPListItem
const listItem: ListItem = _.find( this.props.listItems,(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);
}
// const parentTD = this.getParent(event.target, "TD");
// const attributes: NamedNodeMap = parentTD.attributes;
// const entityid = attributes.getNamedItem("data-entityid").value; // theid of the SPListItem
// const listItem: ListItem = _.find(this.props.listItems, (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);
this.props.getListItems(this.props.listDefinitions, this.props.columns);
}
public componentWillReceiveProps(newProps: IListViewPageProps) {
if (newProps.listDefinitions === this.props.listDefinitions && newProps.columns === this.props.columns) {
return;
}
this.props.getListItems(this.props.listDefinitions);
this.props.getListItems(this.props.listDefinitions, this.props.columns);
}
/**
* Method to get the parent TD of any cell,
@ -208,10 +226,10 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
/**
* Need to fire events here to get data needed for the rerender
*/
const listitem =_.find( this.props.listItems,li => li.GUID === entityid);
const listitem = _.find(this.props.listItems, 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 = _.find(listDef.columnReferences,cr => cr.columnDefinitionId === columnid);
const colref = _.find(listDef.columnReferences, cr => cr.columnDefinitionId === columnid);
if (colref) {// Listname does not have a columnReference
switch (colref.fieldDefinition.TypeAsString) {
@ -244,36 +262,61 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
/**
* This event gets fired to revert any changes made to the ListItem.
*/
public undoItemChanges(event): void {
public HandleUndoItemChangesEvent(event): void {
const parentTD = this.getParent(event.target, "TD"); // 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 = _.find(this.props.listItems,(temp) => temp.GUID === entityid);
const entity: ListItem = _.find(this.props.listItems, (temp) => temp.GUID === entityid);
this.props.undoItemChanges(entity);
}
/**
* This event gets fired, to save the item back to SharePoint.
*/
public updateListItem(event): void {
public updateListItem(entity: ListItem): void {
const listDef: ListDefinition = this.getListDefinition(entity.__metadata__ListDefinitionId);
if (entity.__metadata__ListDefinitionId === entity.__metadata__OriginalValues.__metadata__ListDefinitionId) {// List not changed
switch (entity.__metadata__GridRowStatus) {
case GridRowStatus.toBeDeleted:
this.props.removeListItem(entity, listDef);
break;
case GridRowStatus.new:
case GridRowStatus.modified:
this.props.updateListItem(entity, listDef);
break;
default:
Log.warn("ListItemContainer", "Invalid GrodrowStatus in update ListiteRender-- " + entity.__metadata__GridRowStatus.toString());
}
}
else {// list changed
const oldListDef: ListDefinition = this.getListDefinition(entity.__metadata__OriginalValues.__metadata__ListDefinitionId);
switch (entity.__metadata__GridRowStatus) {
case GridRowStatus.toBeDeleted: // delete from orignal list
this.props.removeListItem(entity, oldListDef);
break;
case GridRowStatus.modified:
entity.__metadata__GridRowStatus = GridRowStatus.new;
/* falls through */
case GridRowStatus.new:// add to new list , delete from orignal list
this.props.updateListItem(entity, listDef).then(response => {
this.props.removeListItem(entity.__metadata__OriginalValues, oldListDef);
});
break;
default:
Log.warn("ListItemContainer", "Invalid GrodrowStatus in update ListiteRender-- " + entity.__metadata__GridRowStatus.toString());
}
}
}
public handleUpdateListItemEvent(event): void {
const parentTD = this.getParent(event.target, "TD");
const attributes: NamedNodeMap = parentTD.attributes;
const entityid = attributes.getNamedItem("data-entityid").value; // theid of the SPListItem
const entity: ListItem = _.find(this.props.listItems,(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);
});
}
const entity: ListItem = _.find(this.props.listItems, (temp) => temp.GUID === entityid);
this.updateListItem(entity);
}
/**
@ -294,7 +337,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
const oldListDef = this.getListDefinition(listItem.__metadata__OriginalValues.__metadata__ListDefinitionId);
for (const newColRef of newListDef.columnReferences) {
// find the old columnReference
const oldColRef = _.find(oldListDef.columnReferences,cr => cr.columnDefinitionId === newColRef.columnDefinitionId);
const oldColRef = _.find(oldListDef.columnReferences, cr => cr.columnDefinitionId === newColRef.columnDefinitionId);
const newFieldName = utils.ParseSPField(newColRef.name).id;
const oldFieldName = utils.ParseSPField(oldColRef.name).id;
switch (newColRef.fieldDefinition.TypeAsString) {
@ -302,8 +345,8 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
// 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 = _.find(this.props.siteUsers,su => su.siteUrl === newListDef.siteUrl);
const newUser =_.find( siteUsersOnNewSite.siteUser,user => user.loginName === name);
const siteUsersOnNewSite = _.find(this.props.siteUsers, su => su.siteUrl === newListDef.siteUrl);
const newUser = _.find(siteUsersOnNewSite.siteUser, user => user.loginName === name);
if (newUser) {
listItem[newFieldName].Id = newUser.id;
listItem[newFieldName].Name = newUser.loginName;
@ -321,7 +364,26 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
}
}
/**
* This method gets called when react cells in the gid get updated.
* This method gets called when user clicks delete listitem.
* 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 markListItemAsDeleted(event) {
const parentTD = this.getParent(event.target, "TD");
const attributes: NamedNodeMap = parentTD.attributes;
const entityid = attributes.getNamedItem("data-entityid").value; // theid of the SPListItem
const listItem: ListItem = _.find(this.props.listItems, (temp) => temp.GUID === entityid); // the listItemItself
if (!listItem.__metadata__OriginalValues) { //SAVE orgininal values so we can undo;
listItem.__metadata__OriginalValues = _.cloneDeep(listItem); // need deep if we have lookup values
}
listItem.__metadata__GridRowStatus = GridRowStatus.toBeDeleted;
this.props.saveListItem(listItem);
}
/**
* This method gets called when cells in the grid 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.
@ -331,9 +393,9 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
private handleCellUpdated(value) {
const {entityid, columnid} = this.state.editing;
const entity: ListItem = _.find(this.props.listItems,(temp) => temp.GUID === entityid);
const entity: ListItem = _.find(this.props.listItems, (temp) => temp.GUID === entityid);
const listDef = this.getListDefinition(entity.__metadata__ListDefinitionId);
const titleColumn =_.find( this.props.columns,c => { return c.type === "__LISTDEFINITIONTITLE__"; });
const titleColumn = _.find(this.props.columns, c => { return c.type === "__LISTDEFINITIONTITLE__"; });
if (titleColumn) {
if (columnid === titleColumn.guid) { // user just changed the listDef,
if (entity.__metadata__GridRowStatus === GridRowStatus.pristine) {
@ -354,7 +416,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
return;
}
}
const columnReference = _.find(listDef.columnReferences,cr => cr.columnDefinitionId === columnid);
const columnReference = _.find(listDef.columnReferences, 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
@ -403,7 +465,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
*/
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 = _.find(this.props.siteUsers,x => {
const siteUsers = _.find(this.props.siteUsers, x => {
return (x.siteUrl === siteUrl);
});
if (siteUsers === undefined) {
@ -418,7 +480,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
*/
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 = _.find(this.props.siteUsers,x => {
const siteUsers = _.find(this.props.siteUsers, x => {
return (x.siteUrl === siteUrl);
});
return siteUsers;
@ -429,7 +491,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
*/
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 =_.find( this.props.lookupOptions,x => {
const lookupoptions = _.find(this.props.lookupOptions, x => {
return (x.lookupField === lookupField) &&
(x.lookupListId === lookupListId) &&
(x.lookupSite === lookupSite) &&
@ -447,7 +509,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
*/
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 = _.find(this.props.lookupOptions,x => {
let lookupoptions = _.find(this.props.lookupOptions, x => {
return (x.lookupField === lookupField) &&
(x.lookupListId === lookupListId) &&
(x.lookupSite === lookupSite) &&
@ -463,7 +525,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
/** The id of the list definition to be retrieved */
listdefid: string
): ListDefinition {
return _.find(this.props.listDefinitions,ld => ld.guid === listdefid);
return _.find(this.props.listDefinitions, ld => ld.guid === listdefid);
}
/**
* This method renders the contents of an individual cell in an editable format.
@ -483,14 +545,20 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
// should I have a different handler for this?
return (
<Dropdown options={opts} selectedKey={entity.__metadata__ListDefinitionId} label=""
onChanged={(selection: IDropdownOption) => { cellUpdated(selection); } } />
onChanged={(selection: IDropdownOption) => { cellUpdated(selection); }} />
);
}
const listDef = this.getListDefinition(entity.__metadata__ListDefinitionId);
const colref = _.find(listDef.columnReferences,cr => cr.columnDefinitionId === column.guid);
const colref = _.find(listDef.columnReferences, cr => cr.columnDefinitionId === column.guid);
const internalName = utils.ParseSPField(colref.name).id;
const columnValue = entity[internalName];
switch (colref.fieldDefinition.TypeAsString) {
case "Counter":// disable editting
return (<span>
{entity[internalName]}
</span>
);
/* falls through */
case "User":
let siteUrl = listDef.siteUrl;
let siteUsers = this.getSiteUsers(siteUrl);
@ -502,7 +570,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
});
const selectedKey = columnValue ? columnValue.Id : null;
return (
<Dropdown label="" options={options} selectedKey={selectedKey} onChanged={(selection: IDropdownOption) => { cellUpdated(selection); } } >
<Dropdown label="" options={options} selectedKey={selectedKey} onChanged={(selection: IDropdownOption) => { cellUpdated(selection); }} >
</Dropdown >
);
case SiteUsersStatus.fetching:
@ -545,7 +613,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
return { key: opt.id, text: opt.value };
});
return (
<Dropdown label="" options={options} selectedKey={(columnValue ? columnValue.Id : null)} onChanged={(selection: IDropdownOption) => { cellUpdated(selection); } } >
<Dropdown label="" options={options} selectedKey={(columnValue ? columnValue.Id : null)} onChanged={(selection: IDropdownOption) => { cellUpdated(selection); }} >
</Dropdown >
);
case LookupOptionStatus.fetching:
@ -613,7 +681,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
return (
<DatePicker strings={datpickerStrings} onSelectDate={cellUpdated} value={date}
allowTextInput={true} isRequired={colref.fieldDefinition.Required}
/>);
/>);
default:
return (
<input autoFocus type="text"
@ -632,7 +700,8 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
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 (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }} >
{listDef.listDefTitle}
</a>);
}
@ -648,7 +717,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
</a>
);
}
const colref =_.find( listDef.columnReferences,cr => cr.columnDefinitionId === column.guid);
const colref = _.find(listDef.columnReferences, cr => cr.columnDefinitionId === column.guid);
if (colref === undefined) { //Column has not been configured for this list
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
'Column Not Defined'
@ -661,12 +730,18 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
case "User":
if (entity[internalName] === undefined) { // value not set
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
</a>
);
} else {
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
{entity[internalName]["Title"]}
</a>
);
@ -675,32 +750,42 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
case "Lookup":
if (entity[internalName] === undefined) { // value not set
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
</a>
);
} else {
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
{entity[internalName][colref.fieldDefinition.LookupField]}
</a>
);
}
/* falls through */
case "Text":
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
{entity[internalName]}
</a>
);
/* falls through */
case "Note":
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} dangerouslySetInnerHTML={{ __html: entity[internalName] }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
dangerouslySetInnerHTML={{ __html: entity[internalName] }} >
</a>
);
/* falls through */
case "DateTime":
let value: string;
if (entity[internalName] === null) {
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
</a>);
}
@ -710,13 +795,23 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
else {
value = entity[internalName];
}
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
{value}
</a>
);
/* falls through */
case "Counter":// disable tabbing to field
return (<span>
{entity[internalName]}
</span>
);
/* falls through */
default:
return (<a href="#" onFocus={this.toggleEditing} style={{ textDecoration: "none" }} >
return (<a href="#" onFocus={this.toggleEditing}
style={{ textDecoration: (entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted) ? "line-through" : "none" }}
>
{entity[internalName]}
</a>
);
@ -747,6 +842,7 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
*/
public TableRow(props: { entity: ListItem, columns: Array<ColumnDefinition>, cellUpdated: (newValue) => void, cellUpdatedEvent: (event: React.SyntheticEvent<any>) => void; }): JSX.Element {
const {entity, columns, cellUpdated, cellUpdatedEvent} = props;
return (
<tr>
{
@ -760,18 +856,22 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
<div>
<Button width="20" style={{ padding: 0 }}
onClick={this.updateListItem} alt="Save to Sharepoint"
onClick={this.handleUpdateListItemEvent} alt="Save to Sharepoint"
buttonType={ButtonType.icon}
icon="Save" disabled={!(entity.__metadata__OriginalValues)} />
<Button width="20" style={{ padding: 0 }}
{/*<Button width="20" style={{ padding: 0 }}
onClick={this.removeListItem}
buttonType={ButtonType.icon}
icon="Delete" />*/}
<Button width="20" style={{ padding: 0 }}
onClick={this.markListItemAsDeleted}
disabled={(entity.__metadata__GridRowStatus === GridRowStatus.toBeDeleted)}
buttonType={ButtonType.icon}
icon="Delete" />
<Button width="20" style={{ padding: 0 }}
// onClick={this.deleteList}
buttonType={ButtonType.icon}
disabled={!(entity.__metadata__OriginalValues)}
onClick={this.undoItemChanges}
disabled={(!(entity.__metadata__OriginalValues))}
onClick={this.HandleUndoItemChangesEvent}
icon="Undo" />
</div>
</td>
@ -814,12 +914,14 @@ class ListItemContainer extends React.Component<IListViewPageProps, IGridState>
{
key: "Undo All changes",
name: "UndoAll",
icon: "Undo"
icon: "Undo",
onClick: this.undoAll
},
{
key: "Save All ",
name: "Save To SharePoint",
icon: "Save"
name: "Save All",
icon: "Save",
onClick: this.saveAll
}]} />

View File

@ -3,7 +3,6 @@ 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';
@ -12,11 +11,11 @@ export interface IPropertyFieldColumnDefinitionsProps {
label: string;
initialValue?: Array<ColumnDefinition>;
onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void;
getColumnDefinitions: () =>Array<ColumnDefinition>;
getColumnDefinitions: () => Array<ColumnDefinition>;
}
export interface IPropertyFieldColumnDefinitionsPropsInternal extends IPropertyPaneCustomFieldProps {
label: string;
initialValue?: Array<ColumnDefinition>;
initialValue?: Array<ColumnDefinition>;
targetProperty: string;
onRender(elem: HTMLElement): void;
onDispose(elem: HTMLElement): void;
@ -34,7 +33,7 @@ class PropertyFieldColumnDefinitionsBuilder implements IPropertyPaneField<IPrope
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.render = this.render.bind(this);
this.properties = _properties;
this.label = _properties.label;
this.properties.onDispose = this.dispose;
@ -45,8 +44,8 @@ class PropertyFieldColumnDefinitionsBuilder implements IPropertyPaneField<IPrope
private render(elem: HTMLElement): void {
const element: React.ReactElement<IPropertyFieldColumnDefinitionsHostProps> = React.createElement(PropertyFieldColumnDefinitionsHost, {
label: this.label,
onPropertyChange: this.onPropertyChange,
columnDefinitions: this.customProperties,
onPropertyChange: this.onPropertyChange,
columnDefinitions: this.customProperties,
});
ReactDom.render(element, elem);
@ -56,6 +55,7 @@ class PropertyFieldColumnDefinitionsBuilder implements IPropertyPaneField<IPrope
}
export function PropertyFieldColumnDefinitions(targetProperty: string, properties: IPropertyFieldColumnDefinitionsProps): IPropertyPaneField<IPropertyFieldColumnDefinitionsPropsInternal> {
//Create an internal properties object from the given properties
var newProperties: IPropertyFieldColumnDefinitionsPropsInternal = {
label: properties.label,
@ -70,6 +70,7 @@ export function PropertyFieldColumnDefinitions(targetProperty: string, propertie
//Calles the PropertyFieldColumnDefinitions builder object
//This object will simulate a PropertyFieldCustom to manage his rendering process
return new PropertyFieldColumnDefinitionsBuilder(targetProperty, newProperties);
}

View File

@ -38,7 +38,7 @@ class PropertyFieldListDefinitionsBuilder implements IPropertyPaneField<IPropert
//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);

View File

@ -3,11 +3,10 @@ import {
MessageBar,
MessageBarType
} from "office-ui-fabric-react";
//const connect = require("react-redux").connect;
import * as redux from "react-redux";
const connect = redux.connect;
import SystemStatus from "../model/SystemStatus";
import Content from "../components/content";
import ListItemContainer from "./ListItemContainer";
interface IAppProps extends React.Props<any> {
systemStatus: SystemStatus;
}
@ -35,15 +34,13 @@ class App extends React.Component<IAppProps, void> {
const { children} = this.props;
return (
<div>
<div>
{this.messageBar(this.props.systemStatus.fetchStatus)}
<div>{this.props.systemStatus.currentAction}
</div>
</div>
<Content isVisible={true}>
{children}
</Content>
<ListItemContainer>
</ListItemContainer>
</div >
);
};

View File

@ -12,7 +12,9 @@ define([], function () {
"ListDefinitionFieldLabel": "List Definitions",
"ListDefinitionsButtonSelect": "Update Lists",
"ListDefinitionsButtonReset": "Clear",
"ListDefinitionsTitle": "List Definitions"
"ListDefinitionsTitle": "List Definitions",
"WebSelectorHeaderText": "Select the Web containging the list"
}
});

View File

@ -17,6 +17,8 @@ declare interface ISpfxReactGridStrings {
ColumnDefinitionsButtonReset:string;
/**The Title on the popupPage */
ColumnDefinitionsTitle:string;
/**The Title on the WebSelector panel */
WebSelectorHeaderText:string;
}
declare module 'spfxReactGridStrings' {
const strings: ISpfxReactGridStrings;

View File

@ -1,10 +1,19 @@
export default class ColumnDefinition {
export enum SortDirection {
None,
Ascending,
Descending
}
export default class ColumnDefinition {
public constructor(
public guid: string,
public name: string,
public width: number,
public editable: boolean = true,
public type: string = "Text",
public sortDirection:SortDirection = SortDirection.None,
public sortSequence:Number=0
)
{ }
}

View File

@ -6,5 +6,5 @@ enum GridRowStatus{
toBeDeleted
}
/**Status of row )mew */
/**Status of row */
export default GridRowStatus;

View File

@ -1,4 +1,8 @@
import ListItem from "../Model/ListItem";
import ColumnDefinition from "../Model/ColumnDefinition";
import { SortDirection } from "../Model/ColumnDefinition";
import ListDefinition from "../Model/ListDefinition";
import * as utils from "../utils/utils";
import * as _ from "lodash";
import {
ADD_LISTITEM,
@ -74,7 +78,68 @@ function saveListItem(state: Array<ListItem>, action: { payload: { listItem: Lis
}
return newarray2;
}
function gotListItems(state: Array<ListItem>, action: { payload: { items: Array<ListItem>, listDefinitions: Array<ListDefinition>, columnDefinitions: Array<ColumnDefinition> } }) {
/** Do Initial Sort here; */
const sortableColumns = _.filter(action.payload.columnDefinitions, cd => {
const x = (cd.sortDirection !== SortDirection.None);
return x;
});
const sortColumns = _.sortBy(sortableColumns, cd => {
return cd.sortSequence;
});
// so a given grid column may be mapped to FieldA in list1 and FieldB in list2, need to find the fields to compare , then compare them
const results = _.union(state, action.payload.items)
.sort((listItem1: ListItem, listItem2: ListItem): number => {
const listDefinition1 = _.find(action.payload.listDefinitions, ld => { return ld.guid === listItem1.__metadata__ListDefinitionId ;});
const listDefinition2 = _.find(action.payload.listDefinitions, ld => { return ld.guid === listItem2.__metadata__ListDefinitionId ;});
for (const sortColumn of sortColumns) {
debugger;
if (sortColumn.type === "__LISTDEFINITIONTITLE__") {
if (listDefinition1.listDefTitle === listDefinition2.listDefTitle) {
return 0;
}
if (sortColumn.sortDirection === SortDirection.Ascending) {
if (listDefinition1.listDefTitle <= listDefinition2.listDefTitle) {
return -1;
} else {
return +1;
}
} else {
if (listDefinition1.listDefTitle >= listDefinition2.listDefTitle) {
return -1;
} else {
return +1;
}
}
}
else {
const columnId = sortColumn.guid;
const list1Column = _.find(listDefinition1.columnReferences, cr => { return cr.columnDefinitionId = columnId; }).name; // internalname#;Displayname
const list1ColumnName = utils.ParseSPField(list1Column).id; // internalname
const list2Column = _.find(listDefinition2.columnReferences, cr => { return cr.columnDefinitionId = columnId; }).name;
const list2ColumnName = utils.ParseSPField(list2Column).id;
if (sortColumn.sortDirection === SortDirection.Ascending) {
if (listItem1[list1ColumnName] <= listItem2[list2ColumnName]) {
return -1;
} else {
return +1;
}
} else {
if (listItem1[list1ColumnName] >= listItem2[list2ColumnName]) {
return -1;
} else {
return +1;
}
}
}
}
});
// return _.union(state, action.payload.items);
return results;
}
function listItemReducer(state = INITIAL_STATE, action: any = { type: "" }) {
switch (action.type) {
case ADD_LISTITEM:
@ -90,7 +155,7 @@ function listItemReducer(state = INITIAL_STATE, action: any = { type: "" }) {
case UNDO_LISTITEMCHANGES:
return undoListItemChanges(state, action);
case GOT_LISTITEMS:
return _.union(state, action.payload.items);
return gotListItems(state, action);
case CLEAR_LISTITEMS:
return [];
default:

View File

@ -7,7 +7,6 @@ import LookupOptionsReducer from "./LookupOptionsReducer";
import SiteReducer from "./SiteReducer";
import SiteUserReducer from "./SiteUsersReducer";
import SystemStatus from "./SystemStatus";
import { routerReducer } from "react-router-redux";
export function RootReducer(state, action) {
const combinedReducers = combineReducers(
{
@ -16,7 +15,6 @@ export function RootReducer(state, action) {
columns: ColumnReducer,
sites: SiteReducer,
pageContext: PageContextReducer,
routing: routerReducer,
systemStatus:SystemStatus,
lookupOptions:LookupOptionsReducer,
siteUsers:SiteUserReducer

View File

@ -5,10 +5,8 @@ import {
Middleware
} from "redux";
import { fromJS } from "immutable";
import { createMemoryHistory } from "react-router";
import { routerMiddleware } from "react-router-redux";
import thunk from "redux-thunk";
import {Store} from "redux"
import {Store} from "redux";
import promiseMiddleware from "redux-promise-middleware";
import { RootReducer } from "../reducers/rootReducer";
@ -28,9 +26,8 @@ function configureStore(initialState) {
}
function _getMiddleware(): Middleware[] {
const history = createMemoryHistory();
let middleware = [
routerMiddleware(history),
promiseMiddleware(),
thunk,
];

View File

@ -1,17 +0,0 @@
import * as React from "react";
//const { IndexRoute, Route } = require("react-router");
import { IndexRoute, Route } from "react-router";
import App from "../containers/app";
import ListDefinitionContainer from "../containers/ListDefinitionContainer";
import ColumnDefinitionContainer from "../containers/ColumnDefinitionContainer";
import ListItemContainer from "../containers/ListItemContainer";
export default (
<Route path="/" component={App}>
<IndexRoute component={ListItemContainer} />
<Route path="/lists" component={ListDefinitionContainer}>
</Route>
<Route path="/columns" component={ColumnDefinitionContainer}>
</Route>
</Route>
);

View File

@ -4,11 +4,9 @@
"redux-promise-middleware": "registry:dt/redux-promise-middleware#0.0.0+20160108032528"
},
"dependencies": {
"react-router": "registry:npm/react-router#2.4.0+20160628165748",
"react-router-redux": "registry:npm/react-router-redux#4.0.0+20160602212457",
"redux-thunk": "registry:npm/redux-thunk#2.0.0+20160525185520"
},
"devDependencies": {
"react-redux": "registry:npm/react-redux#4.4.0+20160614222153"
}
}
}

View File

@ -0,0 +1,110 @@
### 0.6.5 / 2016-05-20
* Don't mutate buffers passed in by the application when masking
### 0.6.4 / 2016-01-07
* If a number is given as input for a frame payload, send it as a string
### 0.6.3 / 2015-11-06
* Reject draft-76 handshakes if their Sec-WebSocket-Key headers are invalid
* Throw a more helpful error if a client is created with an invalid URL
### 0.6.2 / 2015-07-18
* When the peer sends a close frame with no error code, emit 1000
### 0.6.1 / 2015-07-13
* Use the `buffer.{read,write}UInt{16,32}BE` methods for reading/writing numbers
to buffers rather than including duplicate logic for this
### 0.6.0 / 2015-07-08
* Allow the parser to recover cleanly if event listeners raise an error
* Add a `pong` method for sending unsolicited pong frames
### 0.5.4 / 2015-03-29
* Don't emit extra close frames if we receive a close frame after we already
sent one
* Fail the connection when the driver receives an invalid
`Sec-WebSocket-Extensions` header
### 0.5.3 / 2015-02-22
* Don't treat incoming data as WebSocket frames if a client driver is closed
before receiving the server handshake
### 0.5.2 / 2015-02-19
* Fix compatibility with the HTTP parser on io.js
* Use `websocket-extensions` to make sure messages and close frames are kept in
order
* Don't emit multiple `error` events
### 0.5.1 / 2014-12-18
* Don't allow drivers to be created with unrecognized options
### 0.5.0 / 2014-12-13
* Support protocol extensions via the websocket-extensions module
### 0.4.0 / 2014-11-08
* Support connection via HTTP proxies using `CONNECT`
### 0.3.6 / 2014-10-04
* It is now possible to call `close()` before `start()` and close the driver
### 0.3.5 / 2014-07-06
* Don't hold references to frame buffers after a message has been emitted
* Make sure that `protocol` and `version` are exposed properly by the TCP driver
### 0.3.4 / 2014-05-08
* Don't hold memory-leaking references to I/O buffers after they have been
parsed
### 0.3.3 / 2014-04-24
* Correct the draft-76 status line reason phrase
### 0.3.2 / 2013-12-29
* Expand `maxLength` to cover sequences of continuation frames and
`draft-{75,76}`
* Decrease default maximum frame buffer size to 64MB
* Stop parsing when the protocol enters a failure mode, to save CPU cycles
### 0.3.1 / 2013-12-03
* Add a `maxLength` option to limit allowed frame size
* Don't pre-allocate a message buffer until the whole frame has arrived
* Fix compatibility with Node v0.11 `HTTPParser`
### 0.3.0 / 2013-09-09
* Support client URLs with Basic Auth credentials
### 0.2.2 / 2013-07-05
* No functional changes, just updates to package.json
### 0.2.1 / 2013-05-17
* Export the isSecureRequest() method since faye-websocket relies on it
* Queue sent messages in the client's initial state
### 0.2.0 / 2013-05-12
* Add API for setting and reading headers
* Add Driver.server() method for getting a driver for TCP servers
### 0.1.0 / 2013-05-04
* First stable release

View File

@ -0,0 +1,4 @@
# Code of Conduct
All projects under the [Faye](https://github.com/faye) umbrella are covered by
the [Code of Conduct](https://github.com/faye/code-of-conduct).

View File

@ -0,0 +1,383 @@
# websocket-driver [![Build Status](https://travis-ci.org/faye/websocket-driver-node.svg)](https://travis-ci.org/faye/websocket-driver-node)
This module provides a complete implementation of the WebSocket protocols that
can be hooked up to any I/O stream. It aims to simplify things by decoupling the
protocol details from the I/O layer, such that users only need to implement code
to stream data in and out of it without needing to know anything about how the
protocol actually works. Think of it as a complete WebSocket system with
pluggable I/O.
Due to this design, you get a lot of things for free. In particular, if you hook
this module up to some I/O object, it will do all of this for you:
* Select the correct server-side driver to talk to the client
* Generate and send both server- and client-side handshakes
* Recognize when the handshake phase completes and the WS protocol begins
* Negotiate subprotocol selection based on `Sec-WebSocket-Protocol`
* Negotiate and use extensions via the
[websocket-extensions](https://github.com/faye/websocket-extensions-node)
module
* Buffer sent messages until the handshake process is finished
* Deal with proxies that defer delivery of the draft-76 handshake body
* Notify you when the socket is open and closed and when messages arrive
* Recombine fragmented messages
* Dispatch text, binary, ping, pong and close frames
* Manage the socket-closing handshake process
* Automatically reply to ping frames with a matching pong
* Apply masking to messages sent by the client
This library was originally extracted from the [Faye](http://faye.jcoglan.com)
project but now aims to provide simple WebSocket support for any Node-based
project.
## Installation
```
$ npm install websocket-driver
```
## Usage
This module provides protocol drivers that have the same interface on the server
and on the client. A WebSocket driver is an object with two duplex streams
attached; one for incoming/outgoing messages and one for managing the wire
protocol over an I/O stream. The full API is described below.
### Server-side with HTTP
A Node webserver emits a special event for 'upgrade' requests, and this is where
you should handle WebSockets. You first check whether the request is a
WebSocket, and if so you can create a driver and attach the request's I/O stream
to it.
```js
var http = require('http'),
websocket = require('websocket-driver');
var server = http.createServer();
server.on('upgrade', function(request, socket, body) {
if (!websocket.isWebSocket(request)) return;
var driver = websocket.http(request);
driver.io.write(body);
socket.pipe(driver.io).pipe(socket);
driver.messages.on('data', function(message) {
console.log('Got a message', message);
});
driver.start();
});
```
Note the line `driver.io.write(body)` - you must pass the `body` buffer to the
socket driver in order to make certain versions of the protocol work.
### Server-side with TCP
You can also handle WebSocket connections in a bare TCP server, if you're not
using an HTTP server and don't want to implement HTTP parsing yourself.
The driver will emit a `connect` event when a request is received, and at this
point you can detect whether it's a WebSocket and handle it as such. Here's an
example using the Node `net` module:
```js
var net = require('net'),
websocket = require('websocket-driver');
var server = net.createServer(function(connection) {
var driver = websocket.server();
driver.on('connect', function() {
if (websocket.isWebSocket(driver)) {
driver.start();
} else {
// handle other HTTP requests
}
});
driver.on('close', function() { connection.end() });
connection.on('error', function() {});
connection.pipe(driver.io).pipe(connection);
driver.messages.pipe(driver.messages);
});
server.listen(4180);
```
In the `connect` event, the driver gains several properties to describe the
request, similar to a Node request object, such as `method`, `url` and
`headers`. However you should remember it's not a real request object; you
cannot write data to it, it only tells you what request data we parsed from the
input.
If the request has a body, it will be in the `driver.body` buffer, but only as
much of the body as has been piped into the driver when the `connect` event
fires.
### Client-side
Similarly, to implement a WebSocket client you just need to make a driver by
passing in a URL. After this you use the driver API as described below to
process incoming data and send outgoing data.
```js
var net = require('net'),
websocket = require('websocket-driver');
var driver = websocket.client('ws://www.example.com/socket'),
tcp = net.connect(80, 'www.example.com');
tcp.pipe(driver.io).pipe(tcp);
tcp.on('connect', function() {
driver.start();
});
driver.messages.on('data', function(message) {
console.log('Got a message', message);
});
```
Client drivers have two additional properties for reading the HTTP data that was
sent back by the server:
* `driver.statusCode` - the integer value of the HTTP status code
* `driver.headers` - an object containing the response headers
### HTTP Proxies
The client driver supports connections via HTTP proxies using the `CONNECT`
method. Instead of sending the WebSocket handshake immediately, it will send a
`CONNECT` request, wait for a `200` response, and then proceed as normal.
To use this feature, call `driver.proxy(url)` where `url` is the origin of the
proxy, including a username and password if required. This produces a duplex
stream that you should pipe in and out of your TCP connection to the proxy
server. When the proxy emits `connect`, you can then pipe `driver.io` to your
TCP stream and call `driver.start()`.
```js
var net = require('net'),
websocket = require('websocket-driver');
var driver = websocket.client('ws://www.example.com/socket'),
proxy = driver.proxy('http://username:password@proxy.example.com'),
tcp = net.connect(80, 'proxy.example.com');
tcp.pipe(proxy).pipe(tcp, {end: false});
tcp.on('connect', function() {
proxy.start();
});
proxy.on('connect', function() {
driver.io.pipe(tcp).pipe(driver.io);
driver.start();
});
driver.messages.on('data', function(message) {
console.log('Got a message', message);
});
```
The proxy's `connect` event is also where you should perform a TLS handshake on
your TCP stream, if you are connecting to a `wss:` endpoint.
In the event that proxy connection fails, `proxy` will emit an `error`. You can
inspect the proxy's response via `proxy.statusCode` and `proxy.headers`.
```js
proxy.on('error', function(error) {
console.error(error.message);
console.log(proxy.statusCode);
console.log(proxy.headers);
});
```
Before calling `proxy.start()` you can set custom headers using
`proxy.setHeader()`:
```js
proxy.setHeader('User-Agent', 'node');
proxy.start();
```
### Driver API
Drivers are created using one of the following methods:
```js
driver = websocket.http(request, options)
driver = websocket.server(options)
driver = websocket.client(url, options)
```
The `http` method returns a driver chosen using the headers from a Node HTTP
request object. The `server` method returns a driver that will parse an HTTP
request and then decide which driver to use for it using the `http` method. The
`client` method always returns a driver for the RFC version of the protocol with
masking enabled on outgoing frames.
The `options` argument is optional, and is an object. It may contain the
following fields:
* `maxLength` - the maximum allowed size of incoming message frames, in bytes.
The default value is `2^26 - 1`, or 1 byte short of 64 MiB.
* `protocols` - an array of strings representing acceptable subprotocols for use
over the socket. The driver will negotiate one of these to use via the
`Sec-WebSocket-Protocol` header if supported by the other peer.
A driver has two duplex streams attached to it:
* <b>`driver.io`</b> - this stream should be attached to an I/O socket like a
TCP stream. Pipe incoming TCP chunks to this stream for them to be parsed, and
pipe this stream back into TCP to send outgoing frames.
* <b>`driver.messages`</b> - this stream emits messages received over the
WebSocket. Writing to it sends messages to the other peer by emitting frames
via the `driver.io` stream.
All drivers respond to the following API methods, but some of them are no-ops
depending on whether the client supports the behaviour.
Note that most of these methods are commands: if they produce data that should
be sent over the socket, they will give this to you by emitting `data` events on
the `driver.io` stream.
#### `driver.on('open', function(event) {})`
Adds a callback to execute when the socket becomes open.
#### `driver.on('message', function(event) {})`
Adds a callback to execute when a message is received. `event` will have a
`data` attribute containing either a string in the case of a text message or a
`Buffer` in the case of a binary message.
You can also listen for messages using the `driver.messages.on('data')` event,
which emits strings for text messages and buffers for binary messages.
#### `driver.on('error', function(event) {})`
Adds a callback to execute when a protocol error occurs due to the other peer
sending an invalid byte sequence. `event` will have a `message` attribute
describing the error.
#### `driver.on('close', function(event) {})`
Adds a callback to execute when the socket becomes closed. The `event` object
has `code` and `reason` attributes.
#### `driver.addExtension(extension)`
Registers a protocol extension whose operation will be negotiated via the
`Sec-WebSocket-Extensions` header. `extension` is any extension compatible with
the [websocket-extensions](https://github.com/faye/websocket-extensions-node)
framework.
#### `driver.setHeader(name, value)`
Sets a custom header to be sent as part of the handshake response, either from
the server or from the client. Must be called before `start()`, since this is
when the headers are serialized and sent.
#### `driver.start()`
Initiates the protocol by sending the handshake - either the response for a
server-side driver or the request for a client-side one. This should be the
first method you invoke. Returns `true` if and only if a handshake was sent.
#### `driver.parse(string)`
Takes a string and parses it, potentially resulting in message events being
emitted (see `on('message')` above) or in data being sent to `driver.io`. You
should send all data you receive via I/O to this method by piping a stream into
`driver.io`.
#### `driver.text(string)`
Sends a text message over the socket. If the socket handshake is not yet
complete, the message will be queued until it is. Returns `true` if the message
was sent or queued, and `false` if the socket can no longer send messages.
This method is equivalent to `driver.messages.write(string)`.
#### `driver.binary(buffer)`
Takes a `Buffer` and sends it as a binary message. Will queue and return `true`
or `false` the same way as the `text` method. It will also return `false` if the
driver does not support binary messages.
This method is equivalent to `driver.messages.write(buffer)`.
#### `driver.ping(string = '', function() {})`
Sends a ping frame over the socket, queueing it if necessary. `string` and the
callback are both optional. If a callback is given, it will be invoked when the
socket receives a pong frame whose content matches `string`. Returns `false` if
frames can no longer be sent, or if the driver does not support ping/pong.
#### `driver.pong(string = '')`
Sends a pong frame over the socket, queueing it if necessary. `string` is
optional. Returns `false` if frames can no longer be sent, or if the driver does
not support ping/pong.
You don't need to call this when a ping frame is received; pings are replied to
automatically by the driver. This method is for sending unsolicited pongs.
#### `driver.close()`
Initiates the closing handshake if the socket is still open. For drivers with no
closing handshake, this will result in the immediate execution of the
`on('close')` driver. For drivers with a closing handshake, this sends a closing
frame and `emit('close')` will execute when a response is received or a protocol
error occurs.
#### `driver.version`
Returns the WebSocket version in use as a string. Will either be `hixie-75`,
`hixie-76` or `hybi-$version`.
#### `driver.protocol`
Returns a string containing the selected subprotocol, if any was agreed upon
using the `Sec-WebSocket-Protocol` mechanism. This value becomes available after
`emit('open')` has fired.
## License
(The MIT License)
Copyright (c) 2010-2016 James Coglan
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,22 @@
var net = require('net'),
websocket = require('..'),
deflate = require('permessage-deflate');
var server = net.createServer(function(connection) {
var driver = websocket.server();
driver.addExtension(deflate);
driver.on('connect', function() {
if (websocket.isWebSocket(driver)) driver.start();
});
driver.on('close', function() { connection.end() });
connection.on('error', function() {});
connection.pipe(driver.io);
driver.io.pipe(connection);
driver.messages.pipe(driver.messages);
});
server.listen(process.argv[2]);

View File

@ -0,0 +1,95 @@
{
"_args": [
[
{
"name": "websocket-driver",
"raw": "websocket-driver@>=0.5.1",
"rawSpec": ">=0.5.1",
"scope": null,
"spec": ">=0.5.1",
"type": "range"
},
"C:\\Users\\trwg1\\Documents\\GitHub\\sp-dev-fx-webparts3\\samples\\react-multilist-grid\\node_modules\\faye-websocket"
]
],
"_from": "websocket-driver@>=0.5.1",
"_id": "websocket-driver@0.6.5",
"_inCache": true,
"_installable": true,
"_location": "/websocket-driver",
"_nodeVersion": "4.4.4",
"_npmOperationalInternal": {
"host": "packages-12-west.internal.npmjs.com",
"tmp": "tmp/websocket-driver-0.6.5.tgz_1463730072239_0.9899731166660786"
},
"_npmUser": {
"email": "jcoglan@gmail.com",
"name": "jcoglan"
},
"_npmVersion": "2.15.1",
"_phantomChildren": {},
"_requested": {
"name": "websocket-driver",
"raw": "websocket-driver@>=0.5.1",
"rawSpec": ">=0.5.1",
"scope": null,
"spec": ">=0.5.1",
"type": "range"
},
"_requiredBy": [
"/faye-websocket"
],
"_resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz",
"_shasum": "5cb2556ceb85f4373c6d8238aa691c8454e13a36",
"_shrinkwrap": null,
"_spec": "websocket-driver@>=0.5.1",
"_where": "C:\\Users\\trwg1\\Documents\\GitHub\\sp-dev-fx-webparts3\\samples\\react-multilist-grid\\node_modules\\faye-websocket",
"author": {
"email": "jcoglan@gmail.com",
"name": "James Coglan",
"url": "http://jcoglan.com/"
},
"bugs": {
"url": "https://github.com/faye/websocket-driver-node/issues"
},
"dependencies": {
"websocket-extensions": ">=0.1.1"
},
"description": "WebSocket protocol handler with pluggable I/O",
"devDependencies": {
"jstest": "",
"permessage-deflate": ""
},
"directories": {},
"dist": {
"shasum": "5cb2556ceb85f4373c6d8238aa691c8454e13a36",
"tarball": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.5.tgz"
},
"engines": {
"node": ">=0.6.0"
},
"gitHead": "c4494ff88ac196f726bbb77a301c2177124b199e",
"homepage": "https://github.com/faye/websocket-driver-node",
"keywords": [
"websocket"
],
"license": "MIT",
"main": "./lib/websocket/driver",
"maintainers": [
{
"email": "jcoglan@gmail.com",
"name": "jcoglan"
}
],
"name": "websocket-driver",
"optionalDependencies": {},
"readme": "ERROR: No README data found!",
"repository": {
"type": "git",
"url": "git://github.com/faye/websocket-driver-node.git"
},
"scripts": {
"test": "jstest spec/runner.js"
},
"version": "0.6.5"
}