From f137105fb407adc9ed0fbabfd5c1f55262b6555e Mon Sep 17 00:00:00 2001 From: Russell gove Date: Fri, 10 Feb 2017 19:27:24 -0500 Subject: [PATCH] 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 --- samples/react-multilist-grid/package.json | 2 - .../spfxReactGrid/SpfxReactGridWebPart.tsx | 16 +- .../spfxReactGrid/actions/ListItemActions.ts | 27 +- .../spfxReactGrid/components/WebEditor.tsx | 39 -- .../spfxReactGrid/components/WebSelector.tsx | 128 ++++++ .../spfxReactGrid/components/content.tsx | 4 +- .../containers/ColumnDefinitionContainer.tsx | 74 +++- .../containers/ListDefinitionContainer.tsx | 16 +- .../containers/ListItemContainer.tsx | 250 ++++++++---- .../PropertyFieldColumnDefinitions.tsx | 13 +- .../PropertyFieldListDefinitions.tsx | 2 +- .../webparts/spfxReactGrid/containers/app.tsx | 9 +- .../src/webparts/spfxReactGrid/loc/en-us.js | 4 +- .../webparts/spfxReactGrid/loc/mystrings.d.ts | 2 + .../spfxReactGrid/model/ColumnDefinition.ts | 11 +- .../spfxReactGrid/model/gridRowStatus.ts | 2 +- .../spfxReactGrid/reducers/ListItemReducer.ts | 67 ++- .../spfxReactGrid/reducers/RootReducer.ts | 2 - .../spfxReactGrid/store/configure-store.ts | 7 +- .../webparts/spfxReactGrid/store/routes.tsx | 17 - samples/react-multilist-grid/typings.json | 4 +- .../websocket-driver/CHANGELOG.md | 110 +++++ .../websocket-driver/CODE_OF_CONDUCT.md | 4 + .../websocket-driver/README.md | 383 ++++++++++++++++++ .../websocket-driver/examples/tcp_server.js | 22 + .../websocket-driver/package.json | 95 +++++ 26 files changed, 1113 insertions(+), 197 deletions(-) delete mode 100644 samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebEditor.tsx create mode 100644 samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebSelector.tsx delete mode 100644 samples/react-multilist-grid/src/webparts/spfxReactGrid/store/routes.tsx create mode 100644 samples/react-multilist-grid/websocket-driver/CHANGELOG.md create mode 100644 samples/react-multilist-grid/websocket-driver/CODE_OF_CONDUCT.md create mode 100644 samples/react-multilist-grid/websocket-driver/README.md create mode 100644 samples/react-multilist-grid/websocket-driver/examples/tcp_server.js create mode 100644 samples/react-multilist-grid/websocket-driver/package.json diff --git a/samples/react-multilist-grid/package.json b/samples/react-multilist-grid/package.json index 57d989243..021450656 100644 --- a/samples/react-multilist-grid/package.json +++ b/samples/react-multilist-grid/package.json @@ -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", diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx index 226e7d3af..50d133aaf 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/SpfxReactGridWebPart.tsx @@ -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 = configureStore({}); -const history = createMemoryHistory(location); + const App: React.StatelessComponent = () => ( - - {routes} - + ); export default class SpfxReactGridWebPart extends BaseClientSideWebPart { @@ -34,7 +30,7 @@ export default class SpfxReactGridWebPart extends BaseClientSideWebPart { // 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): any { +export function getListItemsAction(dispatch: any, listDefinitions: Array, columnDefinitions: Array): any { dispatch(clearListItems()); - - const promises: Array> = new Array>(); + const promises: Array> = new Array>(); for (const listDefinition of listDefinitions) { if (!listDefinitionIsValid(listDefinition)) { break; @@ -326,7 +330,7 @@ export function getListItemsAction(dispatch: any, listDefinitions: Array { @@ -343,6 +347,7 @@ export function getListItemsAction(dispatch: any, listDefinitions: Array, listDefinitions: Array, columnDefinitions: Array) { return { type: GOT_LISTITEMS, payload: { - items: items + items: items, + listDefinitions: listDefinitions, + columnDefinitions: columnDefinitions } }; } diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebEditor.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebEditor.tsx deleted file mode 100644 index 7cf338d46..000000000 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebEditor.tsx +++ /dev/null @@ -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 { - selectedValue?: string; - onChange(event): void; - webs: Array; -} -export default class WebEditor extends React.Component { - 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 = webs.map((web) => { - return ({ - key: web.url + "#;" + web.title, - text: web.title - }); - }); - options.unshift({ key: null, text: "..Select One" }); - return ( - - - ); - } - - -} - diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebSelector.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebSelector.tsx new file mode 100644 index 000000000..9d7a8d94c --- /dev/null +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/WebSelector.tsx @@ -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; +} +export default class WebSelector extends React.Component { + 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 = 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 = 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 ( +
+ {this.state.openPanel === true ? + +
+ {this.props.siteUrl} +
+
+ {utils.ParseSPField(this.state.selectedWeb).id} +
+
+ {utils.ParseSPField(this.state.selectedWeb).value} +
+ + + +
+ :
+ {utils.ParseSPField(this.state.selectedWeb).value} + +
+ } +
+ ); + } +} + + + diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/content.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/content.tsx index f1a51cbf8..4dd58beba 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/content.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/components/content.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +/*import * as React from 'react'; export interface IContentProps extends React.Props { isVisible: boolean; @@ -11,4 +11,4 @@ export default function Content({ children = null, isVisible }: IContentProps) { { isVisible ? children : null } ); -} +}*/ diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ColumnDefinitionContainer.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ColumnDefinitionContainer.tsx index af167ae7e..ce9d45dd2 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ColumnDefinitionContainer.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ColumnDefinitionContainer.tsx @@ -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 = [ // { 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 = [ // { name: "WorkflowStatus", value: "WorkflowStatus" }, // { name: "WorkflowEventType", value: "WorkflowEventType" }, +]; +const sortDirectionOptions: Array = [ + + { 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 { columns: Array; @@ -59,7 +68,7 @@ export interface IColumnsPageProps extends React.Props { save: () => void; } interface IContextMenu extends React.Props { - // 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 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 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 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 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 cellUpdated(selection.key)} > ); + case "SortSequenceEditor": + return ( + cellUpdated(selection)} + min={1} + max={10} + value={entity[gridColumn.name]} + > + + ); + case "SortDirectionEditor": + return ( + cellUpdated(selection.key)} + > + + ); default: return ( ); + />); } } public CellContents(props: { entity: ColumnDefinition, gridColumn: GridColumn }): JSX.Element { @@ -253,6 +298,13 @@ export class ColumnDefinitionContainerNative extends React.ComponentYes) : (
No
); return result; + case "SortDirectionFormatter": + return ( + + {SortDirection[entity[gridColumn.name]]} + + ); + case "SharePointLookupCellFormatter": return (); default: diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListDefinitionContainer.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListDefinitionContainer.tsx index ea150e9d8..f4c82eb40 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListDefinitionContainer.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListDefinitionContainer.tsx @@ -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); + return ( + + ); case "ListEditor": let lists = this.getListsForWeb(entity);// the Id portion of the WebLookup is the URL return (); 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 ccfe42c73..c61990064 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainer.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/ListItemContainer.tsx @@ -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 { /** 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; + getListItems: (listDefinitions: Array, columnDefinitions: 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 */ @@ -53,7 +53,6 @@ interface IListViewPageProps extends React.Props { 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): void => { - dispatch(getListItemsAction(dispatch, listDefinitions)); + getListItems: (listDefinitions: Array, columnDefinitions: Array): 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 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 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 /** * 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 /** * 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 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 // 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 } } /** - * 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 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 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 */ 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 */ 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 */ 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 */ 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 /** 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 // should I have a different handler for this? return ( { 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 ( + {entity[internalName]} + + ); + /* falls through */ case "User": let siteUrl = listDef.siteUrl; let siteUsers = this.getSiteUsers(siteUrl); @@ -502,7 +570,7 @@ class ListItemContainer extends React.Component }); const selectedKey = columnValue ? columnValue.Id : null; return ( - { cellUpdated(selection); } } > + { cellUpdated(selection); }} > ); case SiteUsersStatus.fetching: @@ -545,7 +613,7 @@ class ListItemContainer extends React.Component return { key: opt.id, text: opt.value }; }); return ( - { cellUpdated(selection); } } > + { cellUpdated(selection); }} > ); case LookupOptionStatus.fetching: @@ -613,7 +681,7 @@ class ListItemContainer extends React.Component return ( ); + />); default: return ( 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 ( + return ( {listDef.listDefTitle} ); } @@ -648,7 +717,7 @@ class ListItemContainer extends React.Component ); } - 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 ( 'Column Not Defined' @@ -661,12 +730,18 @@ class ListItemContainer extends React.Component case "User": if (entity[internalName] === undefined) { // value not set - return ( + return ( ); } else { - return ( + return ( {entity[internalName]["Title"]} ); @@ -675,32 +750,42 @@ class ListItemContainer extends React.Component case "Lookup": if (entity[internalName] === undefined) { // value not set - return ( + return ( ); } else { - return ( + return ( {entity[internalName][colref.fieldDefinition.LookupField]} ); } /* falls through */ case "Text": - return ( + return ( {entity[internalName]} ); /* falls through */ case "Note": - return ( + return ( ); /* falls through */ case "DateTime": let value: string; if (entity[internalName] === null) { - return ( + return ( ); } @@ -710,13 +795,23 @@ class ListItemContainer extends React.Component else { value = entity[internalName]; } - return ( + return ( {value} ); /* falls through */ + case "Counter":// disable tabbing to field + return ( + {entity[internalName]} + + ); + /* falls through */ default: - return ( + return ( {entity[internalName]} ); @@ -747,6 +842,7 @@ class ListItemContainer extends React.Component */ public TableRow(props: { entity: ListItem, columns: Array, cellUpdated: (newValue) => void, cellUpdatedEvent: (event: React.SyntheticEvent) => void; }): JSX.Element { const {entity, columns, cellUpdated, cellUpdatedEvent} = props; + return ( { @@ -760,18 +856,22 @@ class ListItemContainer extends React.Component
@@ -814,12 +914,14 @@ class ListItemContainer extends React.Component { 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 }]} /> diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx index 4e0f12316..acbf3b7c7 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldColumnDefinitions.tsx @@ -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; onPropertyChange(propertyPath: string, oldValue: any, newValue: any): void; - getColumnDefinitions: () =>Array; + getColumnDefinitions: () => Array; } export interface IPropertyFieldColumnDefinitionsPropsInternal extends IPropertyPaneCustomFieldProps { label: string; - initialValue?: Array; + initialValue?: Array; targetProperty: string; onRender(elem: HTMLElement): void; onDispose(elem: HTMLElement): void; @@ -34,7 +33,7 @@ class PropertyFieldColumnDefinitionsBuilder implements IPropertyPaneField 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 = 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 { + //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); + } diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx index 588cbdab7..c3d0e2ad1 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/PropertyFieldListDefinitions.tsx @@ -38,7 +38,7 @@ class PropertyFieldListDefinitionsBuilder implements IPropertyPaneField void; - private customProperties: any; + public constructor(_targetProperty: string, _properties: IPropertyFieldListDefinitionsPropsInternal) { this.render = this.render.bind(this); 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 dd33d26ca..dd51db143 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/app.tsx +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/containers/app.tsx @@ -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 { systemStatus: SystemStatus; } @@ -35,15 +34,13 @@ class App extends React.Component { const { children} = this.props; return (
-
{this.messageBar(this.props.systemStatus.fetchStatus)}
{this.props.systemStatus.currentAction}
- - {children} - + +
); }; 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 69ce3b1cb..a33cc317a 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 @@ -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" } }); \ 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 dcd4879a0..82feec25a 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 @@ -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; diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/ColumnDefinition.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/ColumnDefinition.ts index 20c7abfcc..fe1bf6a6f 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/ColumnDefinition.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/ColumnDefinition.ts @@ -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 ) { } + } diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/gridRowStatus.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/gridRowStatus.ts index d4db761d8..2506907f1 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/gridRowStatus.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/model/gridRowStatus.ts @@ -6,5 +6,5 @@ enum GridRowStatus{ toBeDeleted } -/**Status of row )mew */ +/**Status of row */ export default GridRowStatus; \ No newline at end of file diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ListItemReducer.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ListItemReducer.ts index 972e5d11e..4a035454a 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ListItemReducer.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/ListItemReducer.ts @@ -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, action: { payload: { listItem: Lis } return newarray2; } +function gotListItems(state: Array, action: { payload: { items: Array, listDefinitions: Array, columnDefinitions: Array } }) { + /** 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: diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/RootReducer.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/RootReducer.ts index 3bca15177..8487d1dae 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/RootReducer.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/reducers/RootReducer.ts @@ -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 diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/configure-store.ts b/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/configure-store.ts index b77ae8f63..c83b4b7b1 100644 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/configure-store.ts +++ b/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/configure-store.ts @@ -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, ]; diff --git a/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/routes.tsx b/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/routes.tsx deleted file mode 100644 index 6c16478bb..000000000 --- a/samples/react-multilist-grid/src/webparts/spfxReactGrid/store/routes.tsx +++ /dev/null @@ -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 ( - - - - - - - - -); diff --git a/samples/react-multilist-grid/typings.json b/samples/react-multilist-grid/typings.json index f59e8df8f..1c8862bb3 100644 --- a/samples/react-multilist-grid/typings.json +++ b/samples/react-multilist-grid/typings.json @@ -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" } -} +} \ No newline at end of file diff --git a/samples/react-multilist-grid/websocket-driver/CHANGELOG.md b/samples/react-multilist-grid/websocket-driver/CHANGELOG.md new file mode 100644 index 000000000..f9835a8d7 --- /dev/null +++ b/samples/react-multilist-grid/websocket-driver/CHANGELOG.md @@ -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 diff --git a/samples/react-multilist-grid/websocket-driver/CODE_OF_CONDUCT.md b/samples/react-multilist-grid/websocket-driver/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..4547adff7 --- /dev/null +++ b/samples/react-multilist-grid/websocket-driver/CODE_OF_CONDUCT.md @@ -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). diff --git a/samples/react-multilist-grid/websocket-driver/README.md b/samples/react-multilist-grid/websocket-driver/README.md new file mode 100644 index 000000000..f103abef8 --- /dev/null +++ b/samples/react-multilist-grid/websocket-driver/README.md @@ -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: + +* `driver.io` - 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. +* `driver.messages` - 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. diff --git a/samples/react-multilist-grid/websocket-driver/examples/tcp_server.js b/samples/react-multilist-grid/websocket-driver/examples/tcp_server.js new file mode 100644 index 000000000..2537c3351 --- /dev/null +++ b/samples/react-multilist-grid/websocket-driver/examples/tcp_server.js @@ -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]); diff --git a/samples/react-multilist-grid/websocket-driver/package.json b/samples/react-multilist-grid/websocket-driver/package.json new file mode 100644 index 000000000..22b182a9d --- /dev/null +++ b/samples/react-multilist-grid/websocket-driver/package.json @@ -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" +}