Developing Custom Date Picker, People Picker and Choice (menu to choose from) using AngularJS/jQuery for SharePoint 2013
Introduction
When we need to create a custom form for adding new items or editing items in a list, we may be stuck at some fields like
Date
, People
or Group
and Choice
. The reason behind is: users will likely have Date Picker, People Picker and Dropdown, Radio buttons or check boxes for these kind of fields. We cannot impose them a text input for these kind of fields that they will type the exact value to the text input and then save. If we do so, then it will be total horrible, I suppose. So, the aim of this article is: how can we create these controls for better UX of our users.Initial Setup
Before we dive into this article, the following knowledge is required.
For demo purposes, I am assuming that we have a list called
Task
with the following fields:Title | Single line of text |
DueDate | Date and Time |
AssignedTo | People or Group |
Status | Choice |
Put some values in
Status
field like Running
, Closed
and so on for testing purposes. Now, we need to download some libraries like:- jQuery (download from official site or nuget)
- AngularJs (download from official site or nuget)
- jQuey UI (download from official site or nuget)
- ui-date directive (it will be helpful if you like to develop Date Picker using AngularJs)
- ngTagsInput (it will be helpful if you like to develop People Picker using AngularJs)
- bootstrap-tagsinput (it will be helpful if you like to develop Date Picker using jQuery)
For styling everything, bootstrap should be a proper choice. I am not showing here the steps for creating app or Content Editor Web Part, but in the attachment you will find a SharePoint Hosted App using AngularJs. In this article, I will just discuss the approaches. So before creating these controls, add the above libraries into your project.
Choice
For creating a choice input field, at first, we have to get Choice field values.
Hide Copy Code
var choiceFieldEndPoint = "/_api/web/lists/GetByTitle('List Name')/
fields?$filter=EntityPropertyName eq 'Name of the Choice field'"
If you send a
get
request to the above URL, it will return all values of the Choice
field. In AngularJs, you may write your HTML like the following:
Hide Copy Code
<select class="form-control"
data-ng-model="vm.newTask.status"
data-ng-options="status as status for status in vm.newTask.taskStatusOptions">
</select>
Now in the controller, get
Choice
field values from your service and bind it intovm.newTask.taskStatusOptions
.
Hide Copy Code
demoAppSvc.getTaskStatusOptions()
.then(function (response) {
vm.newTask.taskStatusOptions = response.d.results[0].Choices.results;
});
If your
Choice
field allows multiple selections, then create check box group as shown below:
Hide Copy Code
<label ng-repeat="status in vm.newTask.taskStatusOptions">
<input type="checkbox"
value="{{status}}"
ng-checked="vm.selectedOptions.indexOf(status) > -1"
ng-click="vm.toggleSelection(status)"> {{status}}
</label>
In controller, add the following code to make it work:
Hide Copy Code
vm.selectedOptions = [];
vm.toggleSelection = function toggleSelection(status) {
var index = vm.selectedOptions.indexOf(status);
if (index > -1) {
vm.selectedOptions.splice(index, 1);
} else {
vm.selectedOptions.push(status);
}
};
When you will save value to
Choice
field, write the following code if your Choice
field does not allow multiple selections.
Hide Copy Code
function saveTask(task) {
var data = {
__metadata: {
'type': 'SP.Data.TaskListItem'
},
Status: task.status //It's a string
};
var url = "/_api/web/lists/GetByTitle('Task')/Items";
return baseSvc.postRequest(data, url);
}
If
Choice
field allows multiple selections, then you have to pass an array of string
.
Hide Copy Code
function saveTask(task) {
var data = {
__metadata: {
'type': 'SP.Data.TaskListItem'
},
Status: {
results: task.selectedOptions //It's an array
}
};
var url = "/_api/web/lists/GetByTitle('Task')/Items";
return baseSvc.postRequest(data, url);
}
I am not showing here how
baseSvc.postRequest
has been created. I wrote other two articles about it usingjQuery and AngularJs both.
How can we do that using jQuery, let's see it in action.
Hide Copy Code
<select id="task-status"></select>
Now get
Choice
values by an Ajax request and populate dropdown as shown below:
Hide Copy Code
function createStatusOptions() {
$.ajax({
url: _spPageContextInfo.siteAbsoluteUrl + choiceFieldEndPoint,
type: "GET",
headers: {
"accept": "application/json;odata=verbose",
},
success: function(response) {
var statusOptions = response.d.results[0].Choices.results
var status = $('#task-status');
$.each(statusOptions, function(index, value) {
status.append($("<option>").attr('value', index).text(value));
});
},
error: function(error) {
alert(JSON.stringify(error));
}
});
}
If your
choice
field allows multiple selections, then create a checkbox
group instead of select
using jQuery what exactly I did using AngularJs above.Date and Time Picker
In SharePoint, we can use jQuery UI date picker. It has the same version also in AngularJs. Let's start with AngularJs. Basically, I am recommending this. Before using it, please read the documentation.
Hide Copy Code
<input class="form-control" data-ui-date="vm.dateOptions" data-ng-model="vm.newTask.dueDate" />
ui-date
directive has been used here. You have to pass date option over it as shown below:
Hide Copy Code
vm.dateOptions = {
changeYear: true,
changeMonth: true,
yearRange: '1900:-0'
};
For saving this value into a
Date
and Time
field, you have to convert it as ISOString
.
Hide Copy Code
function saveTask(task) {
var data = {
__metadata: {
'type': 'SP.Data.TaskListItem'
},
Status: task.status,
DueDate: new Date(task.dueDate).toISOString()
};
var url = "/_api/web/lists/GetByTitle('Task')/Items";
return baseSvc.postRequest(data, url);
}
For jQuery, the following demonstrates the HTML and jQuery codes:
Hide Copy Code
<input type="text" id="due-date">
Hide Copy Code
$(function() {
$("#datepicker").datepicker({
changeYear: true,
changeMonth: true,
yearRange: '1900:-0'
});
});
You can add more options that are available here.
People or Group Picker
For this control, I Googled a lot because we know that it's a very complex control. We need to show suggestions while typing in the input field, then remove user from input field and so on. Finally, I manage to find this and this. Basically, this directive allows us add tags in input field and it also shows suggestions while typing in. I have tried to make it as a People or Group Picker. Let's start with AngularJs step by step. First step should be getting suggestions while typing in. We can get suggetions from the following three endpoints.
Hide Copy Code
var userSearchSuggestionEndpoint = "/_api/web/siteusers?$select=Id,
Title&$filter=substringof('some character', Title)";
You can get search suggestion from the above URL which is very fine. But there is a problem: It will be a case sensitive search. I mean if you search with "
Mo
", it will return results that contains "Mo
" in Title
and will not return those that contains "mo
".
Hide Copy Code
var userSearchSuggestionEndpoint =
"/_api/SP.UI.ApplicationPages.ClientPeoplePickerWebServiceInterface.clientPeoplePickerSearchUser";
The above endpoint is very powerful. It can search users from all directories. We can get suggestion by a
POST
request to it.
Hide Shrink
Copy Code
var data = {
queryParams: {
__metadata: {
type: 'SP.UI.ApplicationPages.ClientPeoplePickerQueryParameters'
},
AllowEmailAddresses: true, //This is for the people picker control
//and allows valid email addresses to be resolved and used as values
AllowMultipleEntities: false, //This for the people picker control
//and allows for entering multiple users or groups
AllUrlZones: false, //Only affects the search if you have set the WebApplicationID.
//It search across all UrlZones for that particular web application
MaximumEntitySuggestions: 50, //Basically a row limit of how many users or groups are returned
PrincipalSource: 15, //What sources you wish to search. Choices are All - 15 ,
//Membership Provider - 4 , RoleProvider - 8,
//UserInfoList - 1 or Windows - 2. These values can be combined
PrincipalType: 15, //Controls the type of entities that are returned in the results.
//Choices are All - 15, Distribution List - 2 , Security Groups - 4,
//SharePoint Groups – 8, User – 1. These values can be combined
QueryString: "Your search string" //The term to search
Required: false, //This is for the people picker control and makes the field required.
//It has no effect on the search
SharePointGroupID: null, //An integer representing the group ID you want to limit your search to.
//Only works if you set the Web parameter also which cannot be done via REST
UrlZone: null, // Limits the search to certain zones within the web application.
//Can only be used if the UrlZoneSpecified parameter is set to true.
//Choices are Custom - 3, Default - 0, Extranet - 4, Internet – 2,
//IntraNet – 1. These values can be combined
UrlZoneSpecified: false, //Sets whether you are limiting your search
//to a particular URL zone in the web application.
Web: null, //If set it works in conjunction with the SharePointGroupID parameter.
WebApplicationID: null //String value representing the Guid
//of the web application you want to limit your search to
}
}
It also has a problem that it does not return
People or Group Id
in response. Id
is needed while saving value to the People or Group
field. But it returns LoginName
as key in response and we can get Id
fromLoginName
. See here.
Now the last endpoint I found so far seems to be helpful. It has no case sensitive or
LoginName
issue. It provides us everything what we need for creating a People or Group picker.
Hide Copy Code
function getPeoplePickerSuggestion(searchKey) {
var userSearchSuggestionEndpoint = "/_vti_bin/ListData.svc/UserInformationList?
$select=Id,Name&$filter=substringof('" + searchKey + "',Name)";
return baseSvc.getRequest(userSearchSuggestionEndpoint);
}
Now look at the directive I have used from here. Please read the documentation for more customization.
Hide Copy Code
<tags-input ng-model="vm.newTask.assignedUsers"
display-property="Name"
placeholder="Pick User"
add-from-autocomplete-only="true"
replace-spaces-with-dashes="false">
<auto-complete source="vm.getPeoplePickerSuggestion($query)"></auto-complete>
</tags-input>
Basically, you have to pass array as
ng-model
which will be responsible for all picked users and an another function that returns deferred results as suggestion. See my controller code.
Hide Copy Code
vm.newTask = {
assignedUsers: []
};
vm.getPeoplePickerSuggestion = function(searchString) {
return demoAppSvc.getPeoplePickerSuggestion(searchString)
.then(function(response) {
var results = response.d.results;
return results.filter(function(user) {
return user.Name.toLowerCase().indexOf(searchString.toLowerCase()) != -1;
});
});
};
If your
People or Group
field does not allow multiple selections, then your code should look like the following:
Hide Copy Code
function saveTask(task) {
var data = {
__metadata: {
'type': 'SP.Data.TaskListItem'
},
Title: task.title,
DueDate: new Date(task.dueDate).toISOString(),
Status: task.status,
AssignedToId: 'Id of the selected user'
};
var url = "/_api/web/lists/GetByTitle('Task')/Items";
return baseSvc.postRequest(data, url);
}
Following is for multiple selections. You will need to pass an array of integers instead of a single integer.
Hide Copy Code
function saveTask(task) {
var data = {
__metadata: {
'type': 'SP.Data.TaskListItem'
},
Title: task.title,
DueDate: new Date(task.dueDate).toISOString(),
Status: task.status,
AssignedToId: {
results: [] // construct a array of user Id from selected users or groups
}
};
var url = "/_api/web/lists/GetByTitle('Task')/Items";
return baseSvc.postRequest(data, url);
}
For jQuery version, at first see this document and implement it in your own way.
Hide Copy Code
<input type="text" id="people-picker" />
Hide Copy Code
$('#people-picker').tagsinput({
itemValue: 'Id',
itemText: 'Name',
typeaheadjs: {
name: 'users',
displayKey: 'Name',
source: getPeoplePickerSuggestion() // this function returns user or group suggestions
}
});
Conclusion
In the whole article, I gave you the high level idea. So, download my source code as well and start digging into that. Do not forget to give library references to your project. Otherwise, it will not work
No comments:
Post a Comment