Binding buttons in SPFx

When writing a react based SPFx application one has to make a note that ES6 React.Component doesn’t auto bind methods to itself.

Hence it’s required to manually bind and the following are two ways to do it.

Method 1

onClick={this.addButtonClicked.bind(this)}

import * as React from 'react';

import { Stack, IStackProps, IStackStyles } from 'office-ui-fabric-react/lib/Stack';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { IIconProps } from 'office-ui-fabric-react/';

import { ITestComponentState } from './ITestComponentState';
import { ITestComponentProps } from './ITestComponentProps';

//Stack related styles
const outerStackTokens = {
    childrenGap: 50
};
const addFriendIcon: IIconProps = { iconName: 'Add' };

let outerStackStyles: Partial<IStackStyles> = {};
let innerStackColumnProps: Partial<IStackProps> = {};

export default class TestComponent extends React.Component<ITestComponentProps, ITestComponentState> {
    constructor(props: ITestComponentProps) {
        super(props);
        this.state = {
            items: []
        };
    }
  
    public render(): React.ReactElement<{}> {
        return (
            <div>
                <Stack horizontal tokens={outerStackTokens} styles={outerStackStyles}>
                    <Stack verticalAlign="start" {...innerStackColumnProps}>
                        <Stack.Item align="start" >
                            <ActionButton iconProps={addFriendIcon} onClick={this.addButtonClicked.bind(this)} allowDisabledFocus disabled={this.state.sortItems.length >= 10 ? true : false} >Add Item</ActionButton>
                        </Stack.Item>
                    </Stack>
                </Stack>
            </div>
        );
    }

    private addButtonClicked(event?: React.MouseEvent<HTMLButtonElement>) {
        let itemsOnAdd = this.state.items;
        let itemTitle = "Item " + (this.state.items.length + 1);
        itemsOnAdd.push({ title: itemTitle });
        this.setState({ items: itemsOnAdd });
    }
}

Method 2

this.addButtonClicked = this.addButtonClicked.bind(this);

import * as React from 'react';

import { Stack, IStackProps, IStackStyles } from 'office-ui-fabric-react/lib/Stack';
import { ActionButton } from 'office-ui-fabric-react/lib/Button';
import { IIconProps } from 'office-ui-fabric-react/';

import { ITestComponentState } from './ITestComponentState';
import { ITestComponentProps } from './ITestComponentProps';

//Stack related styles
const outerStackTokens = {
    childrenGap: 50
};
const addFriendIcon: IIconProps = { iconName: 'Add' };

let outerStackStyles: Partial<IStackStyles> = {};
let innerStackColumnProps: Partial<IStackProps> = {};

export default class TestComponent extends React.Component<ITestComponentProps, ITestComponentState> {
    constructor(props: ITestComponentProps) {
        super(props);
        this.state = {
            items: []
        };
        this.addButtonClicked = this.addButtonClicked.bind(this);
    }
  
    public render(): React.ReactElement<{}> {
        return (
            <div>
                <Stack horizontal tokens={outerStackTokens} styles={outerStackStyles}>
                    <Stack verticalAlign="start" {...innerStackColumnProps}>
                        <Stack.Item align="start" >
                            <ActionButton iconProps={addFriendIcon} onClick={this.addButtonClicked} allowDisabledFocus disabled={this.state.sortItems.length >= 10 ? true : false} >Add Item</ActionButton>
                        </Stack.Item>
                    </Stack>
                </Stack>
            </div>
        );
    }

    private addButtonClicked(event?: React.MouseEvent<HTMLButtonElement>) {
        let itemsOnAdd = this.state.items;
        let itemTitle = "Item " + (this.state.items.length + 1);
        itemsOnAdd.push({ title: itemTitle });
        this.setState({ items: itemsOnAdd });
    }
}

React – Call function from HTML Tag with parameter

ReactSometimes we need to call a React function from HTML tags with parameters or arguments and following is the ES6 based way.

Here a button is calling a function updateName with argument newName to set a state which in turns changes the name being displayed.

import React, { Component } from 'react';
import { render } from 'react-dom';
import './style.css';

class App extends Component {
  constructor() {
    super();
    this.state = {
      name: 'Kannan'
    };
  }

  updateName = (newName) =>{
    this.setState({
      name: newName
    })
  }

  render() {
    return (
      <div>
        <p>My Name is {this.state.name}.</p>
        <p>          
          <button onClick={this.updateName.bind(this,'Kannan Balasubramanian')}>Change the name</button><br/>
          <button onClick={()=>this.updateName('Kannan Balasubramanian!')}>Change the name again</button>
        </p>
      </div>
    );
  }
}

render(<App />, document.getElementById('root'));

Do note that using this.updateName.bind() is the recommended way due to performance and efficiency concerns.

You can try the sample output here.

Get query string parameter using JavaScript

Following code will help in fetching the value of a query string parameter.

function GetQueryStringParameter(parameter) {
    var search = location.search.substring(1);
    var queryStringParameters = JSON.parse('{"' + decodeURI(search).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g, '":"') + '"}')
    return queryStringParameters[parameter];
}

Usage:

If URL is http://server/page.html?id=1

Then usage would be GetQueryStringParameters(“id”) which would return 1

Change new item text in SharePoint

The following script changes the “new item” link button in SharePoint view form to whatever we desire.

Add either of the script to content editor web part.

Plain JavaScript version: (This code assumes that there is only one “new item” text in the entire page.)

<script>
    document.addEventListener("DOMContentLoaded",
        function () {
            ExecuteOrDelayUntilScriptLoaded(function () {
                var ReRenderListView_old = ReRenderListView
                ReRenderListView = function (b, l, e) {
                    ReRenderListView_old(b, l, e)
                    changeText()
                }
            }, "inplview.js")
            changeText()
        }
    );

    function changeText() {
        var element = document.querySelector('#idHomePageNewItem span:nth-child(2)')
        element ? (element.innerHTML = "Add item") : null
    }

</script>

jQuery version: (This code can replace any number of “new item” text)

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>

<script>
    $(document).ready(function () {

        var spans = document.getElementsByTagName("span");
        for (var i = 0; i < spans.length; i++) {
            if (spans[i].innerHTML == "new item") {
                spans[i].innerHTML = "add item";
                break;
            }
        }

    });

</script>

Source: sharepoint.stackexchange.com

Remove duplicate list items from SharePoint REST call result using JavaScript

The following code snippet show how to remove duplicate list items in the JSON result of a SharePoint REST call using JavaScript.

Function Definition:

function RemoveDuplicateItems(items, propertyName) {
    var result = [];
    if (items.length > 0) {
        $.each(items, function (index, item) {
            if ($.inArray(item[propertyName], result) == -1) {
                result.push(item);
            }
        });
    }
    return result;
}

Function Usage:
In the below code, assumption is that, the REST call returns data.d.results and the column for which duplicate items need to be removed is Title

var items = data.d.results;
items = RemoveDuplicateItems(items, 'Title');

 

Pass multiple parameters in SetTimeout JavaScript Function

Following is a code snippet which show how to pass multiple parameters in JavaScript’s SetTimeout() function.

setTimeout(function () {
    CustomFunction(param1, param2, param3, param4, param5);
}, 1000);

Load scripts in SharePoint within custom Javascript or Workflow

Following is the code which can be used to load JavaScript in sequence.

This code for example loads the reputation.js from SharePoint’s layouts folder & jQuery from site assets.

(function () {
    ExecuteOrDelayUntilScriptLoaded(function () {
        //sp.runtime.js has been loaded
        ExecuteOrDelayUntilScriptLoaded(function () {
            //sp.js has been loaded
            SP.SOD.registerSod('reputation.js', SP.Utilities.Utility.getLayoutsPageUrl('reputation.js'));
            SP.SOD.registerSod('jquery-3.2.1', '../SiteAssets/Scripts/jquery-3.2.1.min.js');
            SP.SOD.loadMultiple(['reputation.js', 'jquery-3.2.1'], function () {
                //reputation.js & jquery-3.2.1.min.js have been loaded.
                var context = SP.ClientContext.get_current();
                var web = context.get_web();
                //Check if jQuery has been loaded
                if (typeof jQuery != 'undefined') {
                    console.log("Jquery is loaded");
                }
                else {
                    console.log("Jquery is not loaded!");
                }
            });
        }, "sp.js");
    }, "sp.runtime.js");
})();

Source: https://sharepoint.stackexchange.com/questions/92082/uncaught-typeerror-cannot-read-property-get-current-of-undefined

Setting up Node.js & NPM on a machine without administrative privileges and behind a corporate proxy

Recently I was trying to setup a development machine at our office and realized few issues.

  1. The machine didn’t have administrative privileges
  2. It was located behind the corporate proxy
  3. It uses Windows 10 as primary OS

So how to proceed? Following is what I did.

[Update: Now node.js includes npm, so I would suggest to download only node.]

Downloading Node.js & NPM

  1. Download the Node.js binary instead of installer from the below URLs

Node.js binary (32bit or 64 bit): https://nodejs.org/en/download/

  1. Download NPM binary release from the url below

NPM Release: https://github.com/npm/npm/releases

  1. Extract Node.js to D:\Development\Node
  2. Extract NPM to D:\Development\NPM

Set up environment

Every time the development environment is booted do the following

  1. Start a command prompt and set the following path
set PATH=%PATH%;D:\Development\Node;D:\Development\Node\node_modules\npm\bin;
  1. Check the Node version by typing the following
node -v
  1. Check the NPM version by typing the following
npm -v

If you get version numbers for both then both are working.

  1. Now set proxy so that NPM can download modules by running the following
set http_proxy=http://replace-with-your-organization-proxy-url:optional-port-number
set https_proxy=https://replace-with-your-organization-proxy-url:optional-port-number
npm config set strict-ssl false
npm config set proxy http://replace-with-your-organization-proxy-url:optional-port-number
npm config set https-proxy https://replace-with-your-organization-proxy-url:optional-port-number

Now the environment is set up.
Do remember, once the console is closed, all the above settings are lost and needs to be run again, just follow the section “Set up environment” again or do the following.

You can set up the “path” variable without administrator privileges in Windows by doing the following.

  1. From Windows’s Start Menu, open Control Panel.
  2. In Control Panel open “User Accounts”.
  3. In ‘User Accounts” open “Change my environment variables”.
  4. This will open the user’s “Environment Variables” window.
  5. Select the row with entry “Path”.
  6. Click “Edit” button.
  7. In the “Variable value:” text box, append the path of your executable location, which in this case is “D:\Development\Node;D:\Development\Node\node_modules\npm\bin;
  8. Click OK
  9. Open a new terminal or console
  10. Type “node -v” to check if node is working fine.
  11. type “npm -v” to check if npm is working fine.

Source URLs:
http://abdelraoof.com/blog/2014/11/11/install-nodejs-without-admin-rights
http://www.kscodes.com/misc/how-to-set-path-in-windows-without-admin-rights/

Copy new appointments from one Outlook calendar to another Outlook calendar using VBA

This post talks about a code which can copy new appointnments and meetings from one Microsoft Outlook calendar to another Microsoft Outlook calendar.
The code is capable of adding new, updating existing and deleting existing items.

One of the issues I was facing while working with my clients was, they had their own email system and they used to send appointment/meeting requests in those accounts for which I had a separate mail ID. For me it was becoming difficult to track all of them. So I was thinking about a way where all the calendar appointments/meetings across multiple clients get added to my own calendar.

While researching on this, came across a superb post Copy New Appointments to Another Calendar using VBA – Slipstick Systems and made use of it.

The code in the post works as is except for 2 things which I have listed below.

  1. The post uses default calendar as source and I wanted multiple calendar. For this instead of using “GetDefaultFolder” I used “GetFolderPath”. Do note that each instance of calendar required specific functions to be repeated. (I am planning to optimise this code so that the functions remain same but we can use multiple folders.)
  2. The post’s delete functionality was not working due to an issue where the delete function was comparing the GUID with starting character as “[“, which I had to comment out.

Following is the final code which worked for me.
Full credit goes to Diane Poremsky

'Macro to copy calendar items from current default calendar to another calendar
'Source: https://www.slipstick.com/developer/copy-new-appointments-to-another-calendar-using-vba/
Dim WithEvents curCal As Items
Dim WithEvents DeletedItems As Items
Dim newCalFolder As Outlook.folder

Private Sub Application_Startup()
	Dim NS As Outlook.NameSpace
	Set NS = Application.GetNamespace("MAPI")
	' calendar to watch for new items
	Set curCal = NS.GetDefaultFolder(olFolderCalendar).Items 'If you need to use a specific folder then use "NS.GetFolderPath("data-file-name\calendar").Items" and generally "data-file-name" is "user@domain.com"
	' watch deleted folder
	Set DeletedItems = NS.GetDefaultFolder(olFolderDeletedItems).Items 'If you need to use a specific folder then use "NS.GetFolderPath("data-file-name\Deleted Items").Items" and generally "data-file-name" is "user@domain.com"
	' calendar moving copy to
	Set newCalFolder = GetFolderPath("data-file-name\calendar")
	Set NS = Nothing
End Sub

Private Sub curCal_ItemAdd(ByVal Item As Object)
	Dim cAppt As AppointmentItem
	Dim moveCal As AppointmentItem

	' On Error Resume Next

	'remove to make a copy of all items
	If Item.BusyStatus = olBusy Then

		Item.Body = Item.Body & "[" & GetGUID & "]"
		Item.Save

		Set cAppt = Application.CreateItem(olAppointmentItem)

		With cAppt
			.Subject = "Copied: " & Item.Subject
			.Start = Item.Start
			.Duration = Item.Duration
			.Location = Item.Location
			.Body = Item.Body
		End With

		' set the category after it's moved to force EAS to sync changes
		Set moveCal = cAppt.Move(newCalFolder)
		moveCal.Categories = "moved"
		moveCal.Save

	End If
End Sub


Private Sub curCal_ItemChange(ByVal Item As Object)
	Dim cAppt As AppointmentItem
	Dim objAppointment As AppointmentItem

	On Error Resume Next

	' use 2 + the length of the GUID
	strBody = Right(Item.Body, 38)

	For Each objAppointment In newCalFolder.Items
		If InStr(1, objAppointment.Body, strBody) Then
			Set cAppt = objAppointment
		End If
	Next


	With cAppt
		.Subject = "Copied: " & Item.Subject
		.Start = Item.Start
		.Duration = Item.Duration
		.Location = Item.Location
		.Body = Item.Body
		.Save
	End With

End Sub


Private Sub DeletedItems_ItemAdd(ByVal Item As Object)
	' only apply to appointments
	If Item.MessageClass <> "IPM.Appointment" Then Exit Sub
	' if using a category on copied items, this may speed it up.
	If Item.Categories = "moved" Then Exit Sub

	Dim cAppt As AppointmentItem
	Dim objAppointment As AppointmentItem
	Dim strBody As String

	On Error Resume Next

	' use 2 + the length of the GUID
	strBody = Right(Item.Body, 38)
	'If Left(strBody, 1) <> "[" Then Exit Sub 'This particular line didn't work for me

	For Each objAppointment In newCalFolder.Items
		If InStr(1, objAppointment.Body, strBody) Then
			Set cAppt = objAppointment
			cAppt.Delete
		End If
	Next

End Sub


Public Function GetGUID() As String
	GetGUID = Mid$(CreateObject("Scriptlet.TypeLib").GUID, 2, 36)
End Function


Function GetFolderPath(ByVal FolderPath As String) As Outlook.folder
	Dim oFolder As Outlook.folder
	Dim FoldersArray As Variant
	Dim i As Integer

	On Error GoTo GetFolderPath_Error
	If Left(FolderPath, 2) = "\\" Then
		FolderPath = Right(FolderPath, Len(FolderPath) - 2)
	End If
	'Convert folderpath to array
	FoldersArray = Split(FolderPath, "\")
	Set oFolder = Application.Session.Folders.Item(FoldersArray(0))
	If Not oFolder Is Nothing Then
		For i = 1 To UBound(FoldersArray, 1)
			Dim SubFolders As Outlook.Folders
			Set SubFolders = oFolder.Folders
			Set oFolder = SubFolders.Item(FoldersArray(i))
			If oFolder Is Nothing Then
				Set GetFolderPath = Nothing
			End If
		Next
	End If
	'Return the oFolder
	Set GetFolderPath = oFolder
	Exit Function

	GetFolderPath_Error:
		Set GetFolderPath = Nothing
		Exit Function
End Function