Wednesday, September 30, 2015

SharePoint 2013 Send email to multiple users in designer workflow


Requirement: We need a form with fields Title, link, attachments and cc users. On form submit, an email notification should be sent to submitted user and CC to "CC Users"

Developer Solution:
1. Create a list / app with fields;
                                 Title - Single line of text
                                 Link - Hyperlink
                                 Attachments - OOTB
                                 CC Users - Person or Group (Allow Multiple)
 2. Create a designer workflow attached to that list

End Result: NO !!!!!!!!!!!!!!!!!!!

What Happened: If multiple users are present in "CC Users" column then SharePoint designer workflow sends an email to only ONE user. This is a major limitation in SharePoint.

Microsoft Support Response: If you want to send an email to dynamic multiple users then use either mention a group in people column or create a text column and enter the user's email address.
https://support.office.com/en-in/article/Send-e-mail-in-a-workflow-11d5f9dd-955f-412c-b70f-cde4214204f4

Developer Reaction: Whaaaaaaaaaaaaaaaaaaaaaaaaat !!!!!!!!!!!! :@

=======================                           ====================

Not to worry. I have found out a solution for this.

My Investigation: In SharePoint 2013, Once the name is resolved in people picker, DIV element is added dynamically with the ID "divEntityData". There is an another div inside it with the attribute "data". If you look at it carefully, you will see a XML with key-value pair nodes. It contains all necessary information like Title, Department, SIPAddress, Mobile and Email. I have attached a sample below [removed few keys since it is confidential ;) ]. That's it !!!!!!!!!!!! We can make use of it.

<ArrayOfDictionaryEntry xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<DictionaryEntry><Key xsi:type="xsd:string">Title</Key><Value xsi:type="xsd:string">Contractor: SevalHub</Value></DictionaryEntry>
<DictionaryEntry><Key xsi:type="xsd:string">Department</Key><Value xsi:type="xsd:string">IT</Value></DictionaryEntry>
<DictionaryEntry><Key xsi:type="xsd:string">Email</Key><Value xsi:type="xsd:string">dmahendranme@gmail.com</Value></DictionaryEntry>
</ArrayOfDictionaryEntry>

Solution for the same requirement:
1. Create a list / app with fields;
                                 Title - Single line of text
                                 Link - Hyperlink
                                 Attachments - OOTB
                                 CC Users - Person or Group (Allow Multiple)
                                 ccinternal - Multiple line of text (Hide this column to users)

2. Customize the NewForm and EditForm pages.
    - Add custom CSS class to both fields
    [NewForm.aspx sample below. I have added ccemail and ccinternal CSS classes. You should not copy this code. I added it for your reference.]

<tr>
    <td width="190px" valign="top" class="ms-formlabel">
    <H3 class="ms-standardheader">
        <nobr>CC Users</nobr>
    </H3>
    </td>
    <td width="400px" valign="top" class="ms-formbody ccemail">
    <SharePoint:FormField runat="server" id="ff14{$Pos}" ControlMode="New" FieldName="ccusers" __designer:bind="{ddwrt:DataBind('i',concat('ff14',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@ccusers')}"/>
    <SharePoint:FieldDescription runat="server" id="ff14description{$Pos}" FieldName="ccusers" ControlMode="New"/>
    <span class="tip">Enter name(s) of person(s) to receive a copy of this request</span>
    </td>
</tr>

<tr class="ms-hidden"><!-- class="ms-hidden" is to hide our ccinternal field from end users -->
    <td width="190px" valign="top" class="ms-formlabel">
    <H3 class="ms-standardheader">
        <nobr>ccinternal</nobr>
    </H3>
    </td>
    <td width="400px" valign="top" class="ms-formbody ccinternal">
    <SharePoint:FormField runat="server" id="ff22{$Pos}" ControlMode="New" FieldName="ccinternal" __designer:bind="{ddwrt:DataBind('i',concat('ff22',$Pos),'Value','ValueChanged','ID',ddwrt:EscapeDelims(string(@ID)),'@ccinternal')}"/>
    <SharePoint:FieldDescription runat="server" id="ff22description{$Pos}" FieldName="ccinternal" ControlMode="New"/>
    </td>
</tr>

3. Place the below Javascript function in the page. If you are referencing .js then place it in that file.
    [jQuery is used]

function insertccemail()
{
    var emails='';
    $('.ccemail #divEntityData').each(function(){
        var x = $.parseXML($(this).find('div').attr('data'));
        var profiles = x.getElementsByTagName("DictionaryEntry");
        $.each(profiles, function (index, value){if($(value).find('Key').text()=='Email') if($(value).find('Value').text()!='') emails+=$(value).find('Value').text()+'; ';});
    });

    $('.ccinternal textarea').text(emails); // Used textarea because I have created Multiple line of text field for ccinternal

} // insertccemail - Ends


//alert(profiles[0].getElementsByTagName("Value")[0].childNodes[0].nodeValue);  - In Javascript. This alert sample which I tried for testing.

4. Call this insertccemail() function in PreSaveAction(). This PreSaveAction function should be placed inside the form only. You should not place it in .js file and refer it.

function PreSaveAction()
{
    insertccemail();
    if(ValidateFields()) //Never mind. ValidateFields() is a custom Javascript function to validate my fields on Save
       return true;
    else
       return false;
} // PreSaveAction - Ends

5. In designer workflow, refer the "ccinternal" column in CC field.





We are Creators.  Happy Thinking ! :)


Monday, September 28, 2015

SharePoint 2013 Get distinct values of a column in a list or library


I am sure you would have come across the requirement
"Get distinct values of a column in a list or library"

That's why you are here.

What a typical developer usually does for the same requirement;
In jQuery / C#:
- Retrieve the list items
- Create an array object
- Loop all list items
- Compare column value with array
- Store the value in array if there is no match

* I think it is very easy to get distinct values if we use LINQ. I have not tried it.

As always, I came across one application page "filter.aspx" when I was investigating the SharePoint OOTB sorting behavior. :)

What it does? 
- It uses the query string parameters ListIdFieldInternalName, ViewId, FilterOnly and Filter
- Processes and displays a drop down with only distinct values of that column

It works for all columns. I tried with even People/groups column. It's working !!!!!

How to consume those values? [My thought process]
- Provide necessary values for below url

http://<site collection url>/_layouts/15/filter.aspx?ListId={<GUID>}&FieldInternalName=<Column's Internal Name>&ViewId={<GUID>}&FilterOnly=1&Filter=1

Example: https://sample.sharepoint.com/sites/dev/_layouts/15/filter.aspx?ListId={0717D149-0B6A-4657-8DFE-A7A06251F086}&FieldInternalName=Class&ViewId={E1A01D7E-32BB-4AA2-88FC-EE630003AF94}&FilterOnly=1&Filter=1

- Make an ajax call
- Parse and Read the HTML response

* Using jQuery you can easily read the distinct column values

If you want to see the HTML structure of the page, copy and paste the url with all values needed in browser. You shall view the response using browser's developer tool.

Update: Nov 22, 2016
----------------------------
This is my function to parse the ajax response and retrieve the distinct values.
Make sure you have a div element on your page with style="display:none" and ID="tempdiv"
Most importantly do not forget to change the querystring values on url

function getUniqueNames(){
var uniqueNames = [];
$.ajax({
 url:_spPageContextInfo.webAbsoluteUrl+"/_layouts/15/filter.aspx?ListId={0717D149-0B6A-4657-8DFE-A7A06251F086}&FieldInternalName=Class&ViewId={E1A01D7E-32BB-4AA2-88FC-EE630003AF94}&FilterOnly=1&Filter=1",
 method: "GET",
 headers: { "Accept": "application/json; odata=verbose" }
}).then(function(response) {
  $('#tempdiv').html(response);
  $('#tempdiv').find('SELECT option').each(function(i,v){
   if($(v).attr('value')!='')
    uniqueNames.push($(v).attr('value'));
  });
  $('#tempdiv').html('');
});


SharePoint Read values from query string parameter


You will always be wanted to read Query string parameter values using javascript in SharePoint 2013 due to the App models.
I was digging internal files of SharePoint 2013 for an issue and accidentally found out inbuilt javascript function "ReadQueryParam" to read query string parameter values.

You can make use of it. No need to write custom javascript/jQuery functions. If you are not able to call it then copy and paste the below SharePoint's javascript inbuilt function in your application (either in .js file or in your page).

function ReadQueryParam(name) {
    name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
    var results = (new RegExp("[\\?&]" + name + "=([^&#]*)")).exec(window.location.href);
    return (results == null) ? "" : results[1];
}






Tuesday, September 8, 2015

SharePoint Hosted App error: This page can't be displayed

I did not get an opportunity to work on App model in SharePoint 2013 but I wanted to crack. I decided to give it a try from simple (I thought its very simple) SharePoint hosted app.

What I did;
- Opened Visual Studio 2013
- Created new project with template "Apps for SharePoint"
- Decided to deploy it without changing any code but without hitting F5 key
- Packaged the app

Then, what to do ? !!!!! I was wondering how to deploy the .app package manually.
Answer: "App Catalog".

I created a App catalog for my web application because it was not created earlier.
To Create App catalog:
- Go to Central Admin (Farm admin login)
- Navigate to "Apps" from left hand side navigation
- Click "Manage App Catalog" under App Management
Manage App Catalog
- Create a new app catalog site. You must choose your web application. [App catalog is a separate site collection to hold apps.]
Create App catalog

- That's it !!!!!

I uploaded my SharePoint hosted app in newly created app catalog site. Then I went to my site and added the test app [Your app will be listed in your application on "Add an app" page].

After adding my test app, I clicked it. BOOM !!!!!
Error: This page can't be displayed

After research I found DNS is needed. I installed DNS on my DEV machine.
[We need to configure Forward lookup zones, CNAME after installing DNS. Refer: https://technet.microsoft.com/en-us/library/fp161236.aspx ]

I tested my app. Uuh ! Same error.
I gave up and decided to deploy the app from visual studio. I hit F5 and Blast ! Received different error in Visual studio saying it cannot install the app. Whaaaaaaat !

Then packaged and uploaded the app manually through app catalog.
Opened my app. Aaaaaah ! Now different error with correlation ID. I checked SharePoint logs and it was mentioned that "User agent not found......".
Does it need Secure Store Service ? !!!!! I don't know.
I checked my DEV machine and found that Secure Store Service was not created. I have created it and checked my test app. Brought the same error. :) "This page can't be displayed".

Thinking thinking thinking !!!!!

Somewhere I came across the word "hosts" file. I gave it a try.
[Before you proceed, you need to have app instance ID and app URL.]

To find App instance ID:
- Go to Site Contents
- Hover on your App and right click & copy the url
  (Looks like http://<your site>/_layouts/15/appredirect.aspx?instance_id={AAB6792E-0225-4CA0-9D68-BDD970E37ECB})
Instance ID; AAB6792E-0225-4CA0-9D68-BDD970E37ECB

To find App URL:
- Click on your app from your application. (It throws the error)
- Copy the URL from address bar
   (Looks like http://apps-91d6298c4b4348.<site name>/SharePointApp2/Pages/Default.aspx?SPHostUrl=.........)
URL: apps-91d6298c4b4348.<site name>

Edit Hosts file:
- Go to %systemroot%/system32/Drivers/etc
- Create an entry at last.
 
  127.0.0.1 apps-91d6298c4b4348.<site> # AAB6792E-0225-4CA0-9D68-BDD970E37ECB;http://<site>/
::1 apps-91d6298c4b4348.<site> # AAB6792E-0225-4CA0-9D68-BDD970E37ECB;http://<site>/

Ex:
Let's assume my web app is sharepoint and site collection is sharepoint:1425

127.0.0.1 apps-91d6298c4b4348.sharepoint # AAB6792E-0225-4CA0-9D68-BDD970E37ECB;http://sharepoint:1425/
::1 apps-91d6298c4b4348.sharepoint # AAB6792E-0225-4CA0-9D68-BDD970E37ECB;http://sharepoint:1425/

- Save the hosts file

Now opened my test app.
Hurray !!!!!!!!!!!!!

Success !

To consider:
1. App Catalog
2. Make sure the necessary service is running "App Management services and Subscription Settings".
3. DNS
4. Secure Store Service
5. Hosts file [We need to edit it since we are running the app in DEV where it has separate domain]

P.S: I'm lucky enough to get a plain DEV machine. ;)



SharePoint change context menu to other column instead of Title column in View pages

In SharePoint, we can create new View for Lists and Libraries and select the columns that we want to show.
Most of us do not use Title column in List / Libraries. Therefore in views, we use to hide the Title column and SharePoint OOTB provides the context menu for Title column.
Then how does the business user handle the item without context menu !!!!!!!!!! ?

Solution: We need to customize the view in SharePoint Designer.

1. Open the view in Designer (Eg: AllItems.aspx)
2. Goto view and identify your column. Here I have chosen "CustomColumn".
3. Add the below lines inside FieldRef of that column.

LinkToItem="TRUE" LinkToItemAllowed="Required" ListItemMenu="TRUE"

4. Save the view. That's it !!!!! :)