Displaying Rotated Column Headers on an Office UI Fabric Details List within an SPFX Webpart

Sometimes you need to display a grid where the data displayed within the grid’s columns is much narrower than the column headers. For instance you may need to show a user’s name in the column header and just a checkbox in the details indicating that the user has some attribute. In such cases it is often useful to rotate the grids column headers at a 45-degree angle so that more columns can fit on the screen as described here.

 

The link above shows how this can be accomplished in HTML Tables. The Office UI Fabric DetailsList is, however, rendered using <div> tags (at least for now). The <div> tags used to display the DetailsList each have a css class representing their use—for instance ms-DetailsHeader or ms-DetailsHeader-cell. We cannot set these css classes in our application’s app.module.scss because the css clasnames would be renamed to be specific top our application, and fabric would not recognize those class names as noted here.

 

Instead we can add a .scss file to our solution in addition to the .module.scss file. The .scss file won’t have its classes renamed as noted in the link above. Then in the .scss file we can enter the following code:

.ms-DetailsHeader { //* when adjusting line height also need to adjust second parameter to transform:translate below

height: 140px;

white-space: nowrap;

}

//*Set the wrappers around the headers to display at 45degree angles

.ms-DetailsHeader-cell.rotatedColumnHeader{

transform: translate(25px,40px ) //*Change second parameter when adjusting line height

rotate(315deg);

padding-bottom: 90px;

width: 36px !important;

 

 

}

.ms-DetailsHeader-cell{

vertical-align: bottom;

}

.ms-DetailsHeader-cell span {

overflow: visible;

}

This scss sets up the classes to rotate the column headers of any column defined with

headerClassName: “rotatedColumnHeader”,

minWidth: 20,

maxWidth: 20,

With this configuration the columns only take up 20 pixels, bit the headers are displayed at a 45 degree angle so that they are visible.

 

In addition to setting the headerClassName of each column, we also need to import our .scss file into our react component with this code, placed in the react component:

      require(‘./spSecurity.css’);

 

Once this has been set up properly the column headers will display as noted in the article above:

rotadeheaders

 

 

A working example can be found here

 

 

Advertisements
Posted in office-ui-fabric-react, react, spfx | Tagged , , , , , , , | Leave a comment

Using the Office UI Fabric Grouped DetailsList in React-based SPFX Webparts.

Say you have an array of objects in your application that looks like this

export class Widget {

public constructor(

public id: number,

public title: string,

public isActive:string,

public manufacturer:string

) { }

}

And you want to display the items in a react DetailsList  showing the ‘title’ of each item grouped by manufacturer.

 

The JSX to render such a display could be set up like this:

<DetailsList

layoutMode={DetailsListLayoutMode.fixedColumns}

selectionMode={SelectionMode.none}

groups={this.getAvailableWidgetGroups()}

items={this.getAvailableWidgets()}

setKey=”id”

columns={[

{ key: “title”, name: “Widget Name”, fieldName: “title”, minWidth: 20, maxWidth: 100 },

]}

/>

 

 

The getAvailableWidgets is defined as

public getAvailableWidgets(): Array<Widget> {

var tempWidgets = _.filter(this.props.widgets, (widget: Widget) => {

return !this.trContainsWidget(this.state.tr, widget.id);

});

var widgets = _.map(tempWidgets, (widget) => {

return {

title: widget.title,

manufacturer: (widget.manufacturer)?widget.manufacturer:”(none)”,

id: widget.id,

isActive:widget.isActive

};

}).filter((p)=>{return p.isActive===”Yes”});

return _.orderBy(this.state,widgets, [“manufacturer”], [“asc”]);

}

 

The key point is that the list returned must be sorted by whatever fields you want to group on.

 

 

The getAvailableWidgetGroups is defined like this:

public getAvailableWidgetGroups(): Array<IGroup> {

var pigs: Array<Widget> = this.getAvailableWidgets();

var widgetManufactureres = _.countBy(pigs, (p1: Widget) => { return p1.manufacturer; });

var groups: Array<IGroup> = [];

for (const pm in widgetManufactureres) {

groups.push({

name: pm,

key: pm,

startIndex: _.findIndex(pigs, (pig) => { return pig.manufacturer === pm; }),

count: widgetManufactureres[pm],

isCollapsed: true

});

}

return groups;

}

 

The method returns an array of IGroup which is required to group a Detailslist. The IGroup needs the starting row number of each group and the number of element in the group. The method uses lodash to get these values.

 

The lodash countBy function returns an object whose keys are the distinct names of the manufacturers and whose values are the number of rows with that manufacturer.

So to create the array of IGroup, it just loops through each manufacturer and creates an IGroup. It uses the findIndex  function to find the first row with the manufacturer and gets the count from the widgetManufactureres object.

 

Posted in fabric, react, spfx, Uncategorized | Leave a comment

Debugging SPFX Gulp build tasks in VS Code

In VS Code click View==>Debug from the main menu

The Debug box will appear:

debug1

Click the dropdown in the Debug Box and select Add Configuration.

From the list of configurations select {} Node.js: Launch Program:

 

debug2

Create a configuration with the following settings:

{

“type”: “node”,

“request”: “launch”,

“stopOnEntry”: true,

“name”: “node-gulp”,

“args”: [

“bundle”,

“–ship”

],

“program”: “${workspaceRoot}\\node_modules\\gulp\\bin\\gulp.js”,

“cwd”: “${workspaceRoot}”,

“runtimeExecutable”: null,

“env”: {

 

}

},

In the configuration I wanted to debug the Bundle –ship command, so I just entered those strings in the args[].

 

Now, back in the Debug Box, hit the dropdown again and select the configuration you just created (in the example above I called it node-gulp). Then hit the green arrow in the debug box and you will hit a breakpoint on the first line of the gulp.js file:

debug3

You can place additional breakpoints in any of the gulp task .js or .ts files that live in node_modules by clicking in the left margin of the editor. A good starting point is node_modules/@microsoft/sp-build-web/lib/SPWebBuildRig.ts.

 

See the blog post at https://hansrwindhoff.wordpress.com/2015/05/05/debugging-task-runner-tasks-like-gulp-with-visual-studio-code-editordebugger/ for more info.

Good luck!

 

Posted in javascript, react, spfx, Uncategorized | Tagged | Leave a comment

Search-Based Global Navigation in SharePoint On-line

While migrating our Team Sites from SharePoint 2010 to SharePoint On-line we noticed that response time of the sites on SharePoint Online was significantly slower than SharePoint 2010. Researching it we discovered that the issue was caused by our use of structural navigation, and led us to this article about Navigation Options for SharePoint Online.

While the article makes the case that Search based navigation good   because it is security trimmed (as opposed to Managed Navigation), and fast (as opposed to Structural navigation), it states that Search based Navigation can only be implemented with a custom master page. This is incorrect.

I rewrote example script that the article provided to work with standard JavaScript (I removed the need for knockout) and to produce [hopefully] the exact same html that is produced by the out-of-the-box structural navigation provided by SharePoint.   The script is deployed to the SharePoint on-line site using the PnP Add-SPOJavascriptLink, and provides the same navigation experience as Structural Navigation in a fraction of the time.  (In my case response time wen from 5 seconds to under two seconds!)

Because the script produces the same navigation provided by the out of the box structural navigation, it works well with the PNP responsive-UI solution.   The only downside I see is that users cannot reorder the navigation Items,

The Script can be downloaded here and uploaded to a location on your tenant.

To install the script on your site collection run the command:

Add-SPOJavaScriptLink -Name “SearchbasedGlobalNav” -Url “url-of-the-uploaded-script” -Scope Site -Sequence 500

 

Posted in Uncategorized | Leave a comment

Powershell Script to move documents between sitecollections with version history

##see https://shannonbray.wordpress.com/2010/06/26/moving-sharepoint-2007-sites-to-another-environment-with-powershell/

Add-PSSnapin microsoft.sharepoint.powershell -ErrorAction SilentlyContinue

[void][System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint.Deployment”)

$fileName = “exportedWeb.cmp”

$fileLocation = “c:export”

$SourceSiteURL = http://edmsqa.kznsands.local/sites/CP&#8221;

$docid=“CPDOC-46-30”

$DestinationSiteURL = http://edmsqa.kznsands.local/sites/cpccold/&#8221;

$DestinationWebName = “MPPProd” # need to add logic to use rootweb if this is null

$DestinationLibraryName=‘Documents’

##############################################The Export Script

$SourceSite = get-spsite $SourceSiteURL

$provider = [Microsoft.Office.DocumentManagement.DocumentId]::GetProvider($SourceSite)

$result = $provider.GetDocumentUrlsById($SourceSite, $docid)

$sourceWeb = $SourceSite.RootWeb

$file = $sourceWeb.GetFile($result[0])

$item=$file.Item

# Create a new SPExportObject

$exportObject = new-object Microsoft.SharePoint.Deployment.SPExportObject

# Populate the SPExportObject properties

$exportObject.Id = $item.UniqueId;

#$exportObject.IncludeDescendants = [Microsoft.SharePoint.Deployment.SPIncludeDescendants]::All

$exportObject.Type = [Microsoft.SharePoint.Deployment.SPDeploymentObjectType]::File

# Create a new object that holds all of the settings

$settings = new-object Microsoft.SharePoint.Deployment.SPExportSettings

$settings.SiteUrl = $SourceSiteURL

$settings.ExportMethod = [Microsoft.SharePoint.Deployment.SPExportMethodType]::ExportAll

$settings.FileLocation = $fileLocation

$settings.FileCompression = $false

$settings.BaseFileName = $fileName

$settings.ExcludeDependencies = $false

$settings.ExportObjects.Add($exportObject)

$settings.OverwriteExistingDataFile = $true

$settings.IncludeVersions = [Microsoft.SharePoint.Deployment.SPIncludeVersions]::All

# Create a new SPExport object based off the the settings of the SPExportSettings object

$export = new-object Microsoft.SharePoint.Deployment.SPExport($settings)

# Start the export

$export.Run()

# Dispose of the site object to release it from memory

$site.Dispose

#######################################################IMPORT

# Create a new object that holds all of the settings

$settings = new-object Microsoft.SharePoint.Deployment.SPImportSettings

$settings.SiteUrl = $DestinationSiteURL

$settings.FileLocation = $fileLocation

$settings.FileCompression = $false

$settings.RetainObjectIdentity = $false

$settings.UserInfoDateTime = [Microsoft.SharePoint.Deployment.SPImportUserInfoDateTimeOption]::ImportAll

$settings.BaseFileName = $fileName

$settings.CommandLineVerbose=$true

$import = new-object Microsoft.SharePoint.Deployment.SPImport($settings)

$destinationSite = Get-SPSite $destinationSiteURL

$destinationWeb=$destinationSite.OpenWeb($DestinationWebName)

$destinationList= $destinationWeb.Lists[$DestinationLibraryName]

$import.add_Started({ # change the ParentUrl to the root folder of the Targhet LIst

param($Source, $EventArgs)

$rootObject = $EventArgs.RootObjects

ForEach ($importObject in $rootObject) {

if ($importObject.Type -eq [Microsoft.SharePoint.Deployment.SPDeploymentObjectType]::File -or $importObject.Type -eq [Microsoft.SharePoint.Deployment.SPDeploymentObjectType]::ListItem) {

$importObject.TargetParentUrl = $destinationList.RootFolder.ServerRelativeUrl

}

}

})

$import.Run()

 

Posted in Uncategorized | Leave a comment

An AngularJS directive for SharePoint Lookup Columns

Recently I’ve been researching how I can use AngularJS in my SharePoint 2010 environment. Specifically I’ve been working on using the Angular UI-Grid to edit SharePoint lists. I was able to edit most of the columns of  a standard Task list without too much effort, but the ‘Predecessors’ column which is a Lookup column that allows multiple selections was troublesome. I ended up needing to create an AngularJS directive that allows me to work with SharePoint lookup columns (which I am documenting here).  Having gone through that exercise, I’m now envisioning directives to work with all the different column types(sp-lookup, spdate,sp-choice…), directives that display SharePoint views and forms(sp-view,spform,sp-nav), etc.  These directives could even be driven off of the site metadata.

The file spLookup.js defines a module that contains my ‘spLookup directive and a supportingspFieldsDataService Service (spLookup.js can be downloaded from my onedrive here).

The directive is designed to render a Lookup column from a list that was retrieved by calling listdata.svc. If the lookup is a multi-select, you must expand the lookup column when you make your original rest call  to listdata.svc (otherwise the spLookup directive would need to go get the values of the lookup column for each row rendered).It does not work with CSOM. I haven’t tested yet using the SharePoint 2013 _Api endpoint, but it should work.

Here’s how it’s used:

<sp-lookup

controlMode=”edit|new|display”

ng-model=”FieldInTheScope”

multiple

lookupListUrl=”urlOfLookupList”

lookupField=”ColumnToDisplayInTheUI”

modelField=”TheIDColumn”

loadAll=”true”>

</sp-lookup>

The controlMode attribute states whether the control should display in edit, new, or display mode, like it does on the server side. In display mode, the control will just display the values(s) of the lookupField. In edit or new mode the control will display a select control.

The ng-Model attribute names a field on the scope to bind to.

The multiple attribute, if present, allows the user to select multiple values.

The lookupListUrl attribute is the url of the list that is used to display the select box (I also need to add an alternative attribute ‘lookupList’ which could be used if you already have the lookup list in your model, or maybe you want to narrow down the selections).

The lookupField attribute is the name of the field to display in the select control.

The modelField attribute is the name of the field to store in the model for item(s) that are selected. (I should probably just default this to ‘Id’)

The loadAll attribute states whether the sp-lookup control should load all values from the lookupListUrl when it is initialized. If you have no intention of editing any list items, and therefor will not need to render a select control, you can set this to false (I should probably just default this to true!).

To see the control in action create a standard Task list on one of your sites, and add a few tasks to it. Be sure to add a few Predecessors and an Assigned To to each task .In Sharepoint OnLine/Sharepoint 2013  you may need to click the Show more link at the botom of the  form to see the Predecessors column. Also In Sharepoint OnLine/Sharepoint 2013  change the Assigned To column to not Allow multiple selections for this demo.

Upload spLookup.js, spLookupDemo.js,  and spLookupDemo.html into your Site Assets library from here. Also Download angular.js and add it to your Site Assets Library.

For SharePoint 2010:

Create a web-part page to use to test the control. (Site Actions–>More Options–>Page–>Web Part Page )

Add the following scripts to the ContentPlaceholderAdditionalPageHead:

<script type=’text/javascript’ src=’/SiteAssets/angular-1.2.23/angular.js’></script>

<script  src=”/SiteAssets/splookup.js” type=”text/javascript”></script>

<script  src=”/SiteAssets/splookupdemo.js” type=”text/javascript”></script>

You’ll need to adjust the urls based on your environment.

And replace the contents of the ContentPlaceholderMain with this:

<div data-ng-app=”app1″ data-ng-controller=”spLookupDemoCtrl”>

<select size=”5″  ng-options=”Task as Task.Title for Task in tasks”  data-ng-model=”selectedTask”>  </select>

<div ng-show=”selectedTask”>

<table>

<caption>{{selectedTask.Title}}</caption>

<tr>

<td>Predecessors (multi-select) with controlMode display:</td>

<td>

<div data-sp-lookup controlMode=”display” ng-model=”selectedTask.Predecessors.results” multiplelookupListUrl=”../../_vti_bin/listdata.svc/Tasks”

lookupField=”Title” modelField=”Id” loadAll=”true”/>

</td>

.

</tr>

<tr>

<td>Predecessors (multi-select) with controlMode edit:</td>

<td>

<div data-sp-lookup controlMode=”edit” ng-model=”selectedTask.Predecessors.results” multiplelookupListUrl=”../../_vti_bin/listdata.svc/Tasks”

lookupField=”Title” modelField=”Id” loadAll=”true”/>

</td>

</tr>

<tr>

<td>Assigned To (single-select) with controlMode display:</td>

<td>

<div data-sp-lookup controlMode=”display” ng-model=”selectedTask.AssignedToId”lookupListUrl=”../../_vti_bin/listdata.svc/UserInformationList”

lookupField=”Name” modelField=”Id” loadAll=”true”/>

</td>

</tr>

<tr>

<td>Assigned To (single-select) with controlMode edit:</td>

<td>

<div data-sp-lookup controlMode=”edit” ng-model=”selectedTask.AssignedToId” lookupListUrl=”../../_vti_bin/listdata.svc/UserInformationList”

lookupField=”Name” modelField=”Id” loadAll=”true”/>

</td>

</tr>

</table>

<a href=”” ng-click=”saveTask()”>save</a>

</div>

</div>

This html can be copied from the spLookupDemo.html file in the download directory above.

For  Office 365:

Click the Site Actions Gear in the upper right and select Add a Page. Edit the Page, the click the Insert tab, and select ‘Embed Code’

Add the following:

<script src=’../SiteAssets/angular.js’ type=’text/javascript’></script>
<script src=’../SiteAssets/splookup.js’ type=’text/javascript’></script>
<script src=’../SiteAssets/splookupDemo.js’ type=’text/javascript’></script>

Then,  add a Content Editor webpart and point it to ‘SiteAssets/splookupDemo.html’

If all works well, the results will be a sample page demonstrating the use of this directive with both Single and Multi-select columns in both Edit and display mode:

Sosplookupdemo

Some features I need to add to this are pulling templates from the templateCache and allowing them to be overridden,  a lot of error checking, and the ability to handle lookups with fill-in choices.

Posted in Uncategorized | Tagged , , , , , | 5 Comments

AngularJS displaying ng-grid group information inside the grid as in a treeGrid

I had a need to group information in an ng-grid by more than one column (cname, maturity, Buy/Sell and Price), and to display summary data for each group within the grid(not in the ‘label’ format that ng-grid normally uses for aggregate information).

The data coming from my back-end (after my transformResponse) was an array of items that looked like this:

{
“identid”: “100001”,
“cname”: “BA”,

“maturity”: “2014-11-05T05:00:00Z”,

“bs”: “B”,

“price”: “121.19”,

“lots”: 1,

“buylots”: 1,

“selllots”: 0

}

So, in order to aggregate by those multiple fields, i first needed to create a computed field to group by , and then sort the array by that field(in this case using underscore):

_.each(tradelist, function(trade) {
trade.groupCol = trade.cname + trade.maturity + trade.bs + trade.price;
});

$scope.griddata =_.sortBy(tradelist, function(row) {
return row.groupCol;
});

Then I set my gridoptions to group by the newly created groupCol, but not show it:

    $scope
data: griddata
showFilter: false,
width: ‘572px’,
showGroupPanel: false,
groups: [‘groupCol’],
enableRowSelection: true,
multiSelect: false,
showSelectionCheckbox: true,
columnDefs: [
{ field: ‘groupCol’, visible: false },
{ field: ‘cname’, displayName: ‘Contract’, width: ‘150px’ },
{ field: ‘maturity’, displayName: ‘Maturity’, width: ‘112px’, cellFilter: ‘date:\’dd-MMM-yy\” },
{ field: ‘bs’, displayName: ‘B/S’, width: ’50px’ },
{ field: ‘price’, displayName: ‘Price’, width: ‘100px’ },
{ field: ‘identid’, displayName: ‘Trades’, width: ‘100px’ },
{ field: ‘lots’, displayName: ‘Lots’, width: ’60px’ }
]

};

Now the grid was grouping by my combined column, but was displaying the aggregate info in the standard ng-grid format( just a <span> tahg) , not in the grid. What I needed was to display the group information within  the grid, displaying the total of all Lots in the Lots columns and a count of the trades in the trade column.

I first created the function to aggregate the Lots:

$scope.aggregateLots = function(row) {

var lots = 0;

angular.forEach(row.children, function(subrow) {

lots += subrow.entity.lots;

});

return lots;

}

Then I changed the gridOptions.aggregateTemplate to display the data from the first row for each of the grouped columns, and summary data for the Lots and Trade columns:

$scope.gridOptions.aggregateTemplate:
“<div ng-click=\”debugger;customExpand(row,gridId)\” ng-style=\”rowStyle(row)\” class=\”ngAggregate\”>\r” +

“\n” +
“<div class=’ngCell col3 col3t’><div class=’ngCellText col3 col3t’>{{row.children[0].entity.cname}}</div></div>\r\n” +

“<div class=’ngCell col4 col4t’><div class=’ngCellText col4 col4t’>{{row.children[0].entity.maturity | date:\’dd-MMM-yy\’ }}</div></div>\r\n” +

“<div class=’ngCell col5 col5t’><div class=’ngCellText col5 col5t’>{{row.children[0].entity.bs}}</div></div>\r\n” +

“<div class=’ngCell col6 col6t’><div class=’ngCellText col6 col6t’>{{row.children[0].entity.price}}</div></div>\r\n” +

“<div class=’ngCell col7 col7t’><div class=’ngCellText col7 col7t’>{{row.totalChildren()}}</div></div>\r\n” +

“<div class=’ngCell col8 col8t’><div class=’ngCellText col8 col8t’>{{aggregateLots(row)}}</div></div>\r\n” +

“\n” +

“<div class=\”{{row.aggClass()}}\”></div>\r” +
“\n” +
“</div>\r” +
“\n”;

The aggregateTemplate  displays the summary data using the same classes as the detailed rows so that the summary data appears as if it were in the grid:

ng-gridSummarized

A working version with complete code can be found at http://plnkr.co/edit/FQepdgFKtYqAVilvEwXZ?p=preview

Posted in angular, ng-grid | Tagged , , , , , , , | 1 Comment