100% found this document useful (2 votes)
3K views4,494 pages

Sharepoint Development

This document provides an overview of SharePoint development including details on building web parts, extensions, and workflows. It also covers social features, site design, theming, and connecting to APIs.

Uploaded by

david1teague
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (2 votes)
3K views4,494 pages

Sharepoint Development

This document provides an overview of SharePoint development including details on building web parts, extensions, and workflows. It also covers social features, site design, theming, and connecting to APIs.

Uploaded by

david1teague
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Contents

SharePoint development
SharePoint Framework
Getting started
Set up Office 365 tenant
Set up development environment
Web parts
Getting started
Build your first web part
Connect your web part to SharePoint
Deploy your web part to a SharePoint page
Host your web part from Office 365 CDN
Add jQueryUI Accordion to your web part
Use Office UI Fabric React components in your web part
Provision assets from your web part
Deploy your web part to an Azure CDN
Basics
Integrate properties with SharePoint
Configure custom properties
Configure web part icon
Using full-width column
Hide from the toolbox
Preconfigure web parts
Validate properties
Share data between web parts
Tutorial
Extend property pane
Build custom controls
Use cascading dropdowns
Migrate existing customizations
Script Editor web part
jQuery and DataTables
jQuery and FullCalendar
AngularJS applications
Work with __REQUESTDIGEST
Use JSOM
Extensions
Getting started
Build your first extension
Use page placeholders
Deploy your extension to SharePoint
Host extension from CDN
Build Field Customizer
Build ListView Command Set
Configure extension icon
Migrate existing customizations
Client Side Rendering (JSLink)
JavaScript embedding (UserCustomAction)
Edit Control Block (ECB) menu item tutorial
Client Side Rendering (JSLink) tutorial
JavaScript embedding (UserCustomAction) tutorial
Guidance
Enterprise guidance
Team-based development
Governance considerations
Connecting components
External libraries
Add external libraries
JavaScript libraries
CSS styles
SP PnP JS
AngularJS
Connect to APIs
Connect to APIs secured with Azure AD
Call the Microsoft Graph API using OAuth
Connect to APIs secured with Azure AD using the AadHttpClient
Consume enterprise APIs (tutorial)
Consume multi-tenant enterprise APIs (tutorial)
Microsoft Graph
Consume Microsoft Graph (tutorial)
Microsoft GraphHttpClient (Deprecated)
Tutorial (Deprecated)
Setup and deployment
Package solution
Custom build tasks
Extend Webpack
Update packages
Tenant-scoped solution deployment
Tenant properties
Provision SharePoint assets
Optimize builds for production
Localize web parts
SharePoint 2016 support
Use preview capabilities
Design
Design a web part
Reactive and nonreactive web parts
Responsive design
Titles and descriptions
Commanding within a web part
UI text
Placeholders and fallbacks
Empty state of a web part
Accessibility
Key web part examples
Web part showcase
Authoring pages
Design considerations
Custom dialogs
Work with CSS
Themes and colors
Use themes
Office UI Fabric integration
Tools and libraries
Toolchain
Yeoman generator
Debugging
Debug in Visual Studio Code
Debug on modern pages
Roadmap
Known issues and FAQ
Features
Connect to Office 365 group
CSOM development
Hub sites
Create hub sites with PowerShell
PowerShell cmdlets for hub sites
REST API
SP.HubSites.CanCreate
GetById
HubSiteData
HubSites
JoinHubSite
RegisterHubSite
SyncHubSiteTheme
UnRegisterHubSite
SPHubSite type
SPHubSiteData type
SharePoint APIs
SharePoint .NET Server, CSOM, JSOM, and REST API index
Complete basic operations using SharePoint client library code
Complete basic operations using JavaScript library code in SharePoint
Get to know the SharePoint REST service
Complete basic operations
Work with lists and list items
Work with folders and files
Determine endpoint URIs
Use OData query operations
Navigate data structure
Synchronize items
Upload a file
Set custom permissions
Make batch requests
Migration API
Azure container and queue
Encryption
Throttling
Sharing
Site design
Get started
Set scope
Customize default design
JSON schema reference
PowerShell cmdlets
REST API
PnP provisioning engine
Site theming
JSON schema reference
PowerShell cmdlets
CSOM API
REST API
Webhooks
Get started
Reference implementation
Use Azure Functions
List webhooks
Create subscription
Update subscription
Delete subscription
Get subscription
Application Lifecycle Management (ALM) APIs
Column formatting
Communication site
Shorter share links
SharePoint development overview
What's new for developers in SharePoint 2019
SharePoint glossary
Protocol handler error in SharePoint 2016
Programming models
SharePoint Add-ins compared with SharePoint solutions
Build farm solutions
Customize a field type using client-side rendering
URLs and tokens
Virtual directories in solutions
Access SharePoint from mobile and native device apps
Build Windows Phone apps that access SharePoint
Set up an environment for developing mobile apps
Application templates in Visual Studio
List Application template
Create a list app
Store and retrieve list items
Implement business logic and data validation
Support and convert field types
Customize list item queries and filter data
Customize the user interface of a list app
Use multiple lists
Configure and use push notifications
Create a mobile app that contains data from an external data source
Integrate maps with Windows Phone apps and lists
Build search-driven mobile apps with the Navigation and Event Logging REST
interfaces
SharePoint mobile object model
SharePoint mobile client authentication object model
Export the Name field in a Document Library list to a mobile app
Build localized applications based on SharePoint templates
Language support
Build mobile apps for other platforms
Build reusable components
Build sites for SharePoint
What's new with site development
Develop the site design
Design Manager
SharePoint page model
Master pages, the Master Page Gallery, and page layouts
Map a network drive to the Master Page Gallery
Apply a master page to a site
Create a page layout
Customize page layouts for a catalog-based site
Apply styles to page fields
Resolve errors and warnings when previewing a page
Convert an HTML file into a master page
Create a minimal master page
Change the preview page in Design Manager
SharePoint Online Suite Navigation control
Retrieve the URL of a Pages library
Branding and design capabilities
Design packages
Device channels
Display templates
Image renditions
Snippets
Themes
Managed metadata and navigation in SharePoint
Managed navigation
Content Search web part
Use code to pin terms to navigation term sets
Publish SharePoint sites
Cross-site publishing in SharePoint
User segmentation in SharePoint
Minimal Download Strategy overview
Modify SharePoint components for MDS
Optimize page performance
Optimize site accessibility
Add SharePoint capabilities
Workflows
Get started with workflows
Workflow fundamentals
What's new in workflows
Set up a workflow development environment
Configure MSMQ for workflows
Workflow initiation and configuration properties
Workflow development best practices
Debugging workflows
Using the pairing cmdlet Register-SPWorkflowService
Workflow samples
Develop SharePoint workflows using Visual Studio
Create a workflow app
Create workflows
SharePoint Workflow Services CSOM
Web Services in workflows
Tasks in workflows
Create custom SharePoint workflow forms
Build and deploy workflow custom actions
Use workflow interop for SharePoint
Common error messages in workflow development
SharePoint workflow object model
Develop workflows in SharePoint Designer and Visio
What's changed in SharePoint Designer
Create a workflow by using SharePoint Designer and the SharePoint Workflow
platform
Shapes in the SharePoint Server workflow template in Visio
Troubleshoot SharePoint Server workflow validation errors in Visio
Dictionary actions in SharePoint Designer
Coordination actions in SharePoint Designer
Task Actions in SharePoint Designer
Eventing Actions in SharePoint Designer
Visual Designer for workflow in SharePoint Designer
Web Services in SharePoint workflows using SharePoint Designer
Package and deploy workflow in SharePoint
Create a workflow with elevated permissions
Match the SharePoint Designer version with the farm version
Transfer a workflow between SharePoint Designer and Visio Professional
Workflow actions and activities reference
Workflow activity classes
Workflow actions quick reference
Workflow actions available using the workflow interop bridge
Workflow conditions quick reference (SharePoint 2010)
Workflow actions quick reference (SharePoint 2010)
Visio shapes in SharePoint Designer (SharePoint 2010)
Validation issues in Visio (SharePoint 2010)
Social and collaboration
What's new for developers
Get started developing with social features
Read and write to the social feed by using the .NET client object model
Read and write to the social feed by using the REST service
Social feed REST API reference
Following people and content REST API reference
Work with social feeds
Create and delete posts using the .NET client object model
Create and delete posts using the JavaScript object model
Include mentions, tags, and links to sites and documents
Embed images, videos, and documents
Reference threads and digest threads in feeds
Follow people
Follow people using the .NET client object model
Follow people using the JavaScript object model
Follow content
Follow documents and sites using the .NET client object model
Follow documents, sites, and tags using the REST service
Work with user profiles
Retrieve user profile properties using the .NET client object model
Retrieve user profile properties using the JavaScript object model
Work with user profiles using the server object model
Location and map functionality
Add a Geolocation column to a list programmatically
Create a map view for the Geolocation field
Set the Bing Maps key at the web and farm level
Extend the Geolocation field type by using client-side rendering
Search in SharePoint
What's new in SharePoint search for developers
Search new content
Search connector framework
Enhance the BDC model file for Search
Crawl associated external content types
Crawl binary large objects (BLOBs)
Configure item-level security
Configure search
Custom word breakers
Custom content processing with the Content Enrichment web service callout
Use the web service callout
Trigger expression syntax
Build search queries
Keyword Query Language (KQL) syntax reference
FAST Query Language (FQL) syntax reference
Using the SharePoint search Query APIs
Search REST API
Retrieve query suggestions using the Search REST service
Search add-ins
Customize search results
Sort search results
Customize ranking models
Custom security trimming
Use a custom security trimmer for search results
Export and import search configuration settings programmatically
Refine queries
Business Connectivity Services (BCS)
What's new in BCS
Get started with BCS
Set up a development environment for BCS
External content types
Create external content types for SQL Server
Create associations in SharePoint
Add-in-scoped external content types
Create an add-in-scoped external content type
Convert an add-in-scoped external content type to tenant-scoped
Access external data by using REST
Use OData sources with BCS
Create an external content type
Create an OData data service for use as a BCS external system
Create an external list
External events and alerts
Create external event receivers
Get started using the client object model with external data
Use the client code library to access external data
BCS programmers reference
BCS REST API reference
BCS client object model reference
Changes in the BDC model schema
BDC model schema reference
Create hybrid connectivity apps for SharePoint
Develop with Duet Enterprise 2.0
Use SAP workflow
Use SAP reporting
Office and SharePoint application services
Excel Services
Getting started
Overview
Architecture
Development roadmap
Features
Blogs, forums, and resources
Excel Web Services
What's new
Access the SOAP API
Loop-back SOAP calls and direct linking
Develop a custom application
Error codes
Get values from ranges
Set values of ranges
Specify a range address and sheet name
Get an entire workbook or a snapshot
Use the CloseWorkbook method call asynchronously
Set various credentials
Refresh data
Use the SubCode property to capture error codes
User-defined functions (UDFs)
Understand UDFs
Develop a managed-code UDF
FAQ about UDFs
Enable UDFs
Create a UDF that calls a web service
Access an external data source from a UDF
Deploy UDFs using SharePoint Foundation solutions
Restrict UDF code access security permissions
Excel Web Access
Programmatically add a web part to a page
Locate and copy required SharePoint DLLs
ECMAScript
Develop using the Content Editor web part
ECMAScript overview
JavaScript UDFs overview
JavaScript object model
Create a mashup that uses an embedded workbook and Bing Maps
Excel Services REST API
REST API overview
Basic URI structure and path
Discovery
Resources URI
Getting ranges using Atom feed and HTML fragment
Sample URI
Access a schema
Unsupported features
Advanced scenarios and additional samples
Use OData with REST in SharePoint
Request Excel workbook data from SharePoint Server using OData
General guidelines
Alerts
Known issues and tips
Best practices
Trust a location
Save from Excel client to the server
Save to the server to prepare for programmatic access
Catch exceptions
Remove Excel Interactive View from a webpage
Machine Translation Service
PerformancePoint Services
Create report renderers
Create report editors
Create filter data providers
Create filter editors
Create tabular data source providers
Create tabular data source editors
Create scorecard transforms
PowerPoint Automation Services
Access web apps
What's new in Access
Visio Services
Word Automation Services
Extend the fixed-format export feature
Authentication, authorization, and security
Authorization, users, groups, and the object model
Role, inheritance, elevation of privilege, and password changes
Claims-based identity
Sign in to SharePoint
Claims provider overview
Create a claims provider
Deploy a claims provider
Claims-based identity and concepts
Sample delegation, federation, and authentication scenario
Claims-based identity term definitions
Configuration, administration, and resources
eDiscovery
What's new in eDiscovery and compliance
CMIS
XLIFF interchange file format
Create no-code solutions
SharePoint Composites
Save a site as a template
Upgrade site customizations
Upgrade web templates
Use Feature upgrade to apply new master pages
Set up a development environment for SharePoint
How-tos for SharePoint
Code samples for SharePoint
Choose the right API set
Use the Office 365 content delivery network (CDN)
Use the site collection app catalog
Maintenance mode for client-side web parts
SharePoint Workflow Manager
SharePoint Application Lifecycle Management
Accessibility
Detect the installed SKU
Avoid getting throttled or blocked in SharePoint Online
Volume Shadow Copy Service (VSS)
VSS Writer
VSS requestors
Create a VSS requestor
Back up and restore SharePoint
Back up and restore a search service application
SharePoint Add-ins
Get started creating SharePoint-hosted add-ins
1. Deploy and install add-ins
2. Add custom columns
3. Add a custom content type
4. Add a web part to a page
5. Add a workflow
6. Add a custom page and style
7. Add custom client-side rendering
8. Create a custom ribbon button in the host web
9. Use the SharePoint JavaScript APIs to work with SharePoint data
10. Work with host web data from JavaScript in the add-in web
Get started creating provider-hosted add-ins
1. Give your add-in the SharePoint look-and-feel
2. Include a custom button
3. Get an overview of the SharePoint object model
4. Add write operations
5. Include an add-in part
6. Handle add-in events
7. Add first-run logic
8. Programmatically deploy a custom button
9. Handle list item events
Design
Design options for add-ins
Add-in architecture and development landscape
Patterns for developing and hosting an add-in
Host webs, add-in webs, and SharePoint components
Secure data access and client object models for add-ins
UX design for add-ins
SharePoint Add-ins UX design guidelines
Develop
Explore the app manifest structure and the package of an add-in
URL strings and tokens in add-ins
Create UX components in SharePoint
Use a SharePoint website's style sheet in add-ins
Use the client chrome control in add-ins
Create add-in parts to install with your add-in
Create custom actions to deploy with add-ins
Customize a list view in add-ins using client-side rendering
Use the client-side People Picker control in SharePoint-hosted add-ins
Highlight content and enhance the functionality of SharePoint-hosted add-ins with
the callout control
Include a web part in a webpage on the add-in web
Office Web Widgets - Experimental overview
Use the experimental People Picker widget in add-ins
Use the experimental Desktop List View widget in add-ins
Office Web Widgets - Experimental License Terms
Work with external data in SharePoint
Create a custom proxy page for the cross-domain library
Query a remote service using the web proxy
Handle events in add-ins
Create a remote event receiver
Create an add-in event receiver
Debug and troubleshoot a remote event receiver
Create a provider-hosted add-in that includes a custom SharePoint list and content
type
Get user identity and properties in SharePoint
Localize SharePoint Add-ins
Authorization and authentication of add-ins
Add-in permissions in SharePoint
Register add-ins
Add-in authorization policy types in SharePoint
Three authorization systems for add-ins
Creating add-ins that use low-trust authorization
Context Token OAuth flow for add-ins
Authorization Code OAuth flow for add-ins
Handle security tokens in provider-hosted low-trust add-ins
Use an Office 365 SharePoint site to authorize provider-hosted add-ins on an
on-premises SharePoint site
Replace an expiring client secret in an add-in
Creating add-ins that use high-trust authorization
Create high-trust add-ins
Create and use access tokens in provider-hosted high-trust add-ins
Troubleshooting high-trust add-ins
Package and publish high-trust add-ins
Creating add-ins that use the cross-domain library
Access SharePoint data from add-ins using the cross-domain library
Work with the cross-domain library across different Internet Explorer security
zones in SharePoint Add-ins
Create add-ins that can be used by anonymous users
Convert an autohosted add-in to a provider-hosted add-in
Create add-ins for the SAP Gateway for Microsoft
Create an add-in that contains a document template and a task pane add-in
Publish
Deploy and install add-ins
Tenancies and deployment scopes for add-ins
SharePoint Add-ins update process
Update add-ins
Update add-in web components in SharePoint
Update host web components in SharePoint
Update remote components in add-ins
Create a handler for the update event in add-ins
Publish add-ins by using Visual Studio
Tools
Set up a development environment for add-ins on Office 365
Create a developer site on an existing Office 365 subscription
Set up an on-premises development environment for add-ins
Create add-ins in Visual Studio
Solution guidance
Modernizing your classic SharePoint sites
Modernize the user interface
Maximize use of modern lists and libraries
Analyze and use the scanner data
Rollout approaches
Transform classic pages to modern client-side pages
Page transformation model
Page layout mapping
Page transformation API
Modernize customizations
Modernize site branding
Connect to an Office 365 group
Analyze and use the scanner data
Site permissions after Office 365 group connection
Branding and site provisioning solutions
Page model
Development and design tools and practices
Metadata, site navigation, and publishing site features
Site branding
Use composed looks to brand sites
Use remote provisioning to brand pages
Use CSS to brand pages
Customize elements of a page
Update the branding of existing sites and regions
Customize OneDrive for Business sites
Site provisioning
Implement a site classification solution
Modify host web lists at creation time
Create content types by using CSOM
Modify site permissions and get external sharing status
Manage users and groups
UX components
Customize the UX
Create UX controls
Improve performance
Customizing the "modern" experiences in SharePoint Online
Provision "modern" team sites programmatically
Customize "modern" team sites
Customize "modern" lists and libraries
Customize "modern" site pages
SharePoint "modern" sites classification
OneDrive and SharePoint Online Multi-Geo
Permissions model
Discover your tenant configuration
Access OneDrive for Business
Work with sites
Provision classic team sites
Manage apps/add-ins
Work with user profiles
Search in a tenant
Manage metadata
Define and publish content types
Connect to external data using BCS and Secure Store Service
Set up a sample application
Building SharePoint Online portals
Performance
Information architecture
Navigation
Content aggregation
Branding
Deployment
Composite business add-ins
Migrate InfoPath forms
Data storage options
Corporate event add-in integration
Call web services from workflows
ECM solutions
Document library templates
Auto-tagging
Information management
Records management extensions
Taxonomy operations
Bulk upload documents
Upload large files
Synchronize term groups
Support % and # in files and folders with ResourcePath API
Localization solutions
Use localization features
Localize UI elements
Search solutions
Search customizations
Security and performance
Authorization considerations for tenants hosted in Germany, China, or US
Handle SharePoint Online throttling
Set up app-only access to SharePoint
Grant access using Azure AD app-only
Grant access using SharePoint app-only
Implement a web app policy alternative
Migrate from permissive to strict tenant setting
Authorize provider-hosted add-in users at run time
Cross-domain images in provider-hosted add-ins
Elevated privileges in SharePoint Add-ins
Provide add-in app-only tenant administrative permissions in SharePoint Online
Develop using tenant permissions with app-only
Set external sharing in Office 365
JavaScript patterns and performance
SharePoint Add-in recipes
App-only and elevated privileges
Branding sites
Custom actions
Custom field type
Custom ribbons
Customize your site UI using JavaScript embedding
Delegate controls
Document ID provider
Event receivers and list event receivers
Feature stapling
Information management policy
JavaScript customizations
List definition/list template
List instance
Localization
Master pages
MMS manipulation
Modules
OneDrive for Business customization
Performance considerations
Remote timer jobs
Remote event receivers
Search API usage
Search configuration
SharePoint change log
Site columns and content types
Site provisioning
Asynchronous operations in SharePoint Add-ins
User controls and web controls
User profile manipulation
Variations
Web part
Upload web parts
Connect SharePoint Add-in parts
Workflows, actions (activities), events, and forms
Yammer integration
Transforming farm solutions
Replace content types and site columns
Replace files
Replace lists
Replace web parts
Transforming sandbox solutions
Replace web parts
Replace event receivers
Replace feature receivers
Fix code-based InfoPath forms
User profile solutions
Read or update user profile properties
Bulk update custom user profile properties
Migrate user profile properties
Personalize search results
Upload user profile pictures
Deploying your SharePoint Add-ins
Deploy sites to Azure
Use Azure WebJobs with Office 365
Configure provider-hosted add-ins for distribution
Configure Office 365 projects for distribution
PnP remote provisioning
PnP provisioning engine
PnP provisioning framework
PnP provisioning engine and the Core library
PnP provisioning schema
Provisioning console application sample
PnP remote timer job framework
PnP timer job framework
Create remote timer jobs
Get started with WebJobs
PnP Office 365 CLI
PnP PowerShell reference
PnP Sites Core API reference
PnP Sites Core API reference - Extension methods
SharePoint schema reference
SharePoint Add-in schemas
Schema reference for manifests of SharePoint Add-ins
Schema map (SharePoint Add-in Manifest)
Elements (SharePoint Add-in Manifest)
App element
AppPermissionRequest element
AppPermissionRequests element
AppPrerequisite element
AppPrerequisites element
AppPrincipal element
AutoDeployedWebApplication element
DebugInfo element
InstalledEventEndpoint element
Internal element
Properties element
Property element
RemoteEndpoint element
RemoteEndpoints element
RemoteWebApplication element
SettingsPage element
StartPage element
SupportedLanguages element
SupportedLocale element
SupportedLocales element
Title element
UninstallingEventEndpoint element
UpgradedEventEndpoint element
WebTemplate element
Types (SharePoint Add-in Manifest)
AppDefinition complexType
AppPermissionPropertyDefinition complexType
AppPermissionRequestDefinition complexType
AppPermissionRequestsDefinition complexType
AppPrerequisite complexType
AppPrerequisiteCollection complexType
AppPrincipalDefinition complexType
AutoDeployedWebApplicationDebugInfoDefinition complexType
PropertiesDefinition complexType
RemoteEndpointDefinition complexType
RemoteEndpointsDefinition complexType
SupportedLocaleDefinition complexType
SupportedLocalesDefinition complexType
UrlElementDefinition complexType
WebTemplateDefinition complexType
AppPermissionAppPrincipalDefinition simpleType
AppPermissionRightDefinition simpleType
AppPrerequisiteTypeDefinition simpleType
AppPrincipalTypeDefinition simpleType
AutoDeployedWebApplicationDebugInfoAppUrlDefinition simpleType
CultureNameDefinition simpleType
DescriptionDefinition simpleType
GUID simpleType
ManifestUri simpleType
NameDefinition simpleType
RestrictedInt simpleType
SupportedLanguagesDefinition simpleType
TitleDefinition simpleType
TypeDefinition simpleType
VersionDefinition simpleType
WebTemplateIdDefinition simpleType
AppHostWebFeatures schema reference
Schema map (AppHostWebFeatures)
Elements (AppHostWebFeatures)
ActivationDependencies element
ActivationDependency element
ApplyElementManifests element
Button element
CheckBox element
ClientWebPart element
ColorPicker element
ComboBox element
CommandUIDefinition element
CommandUIDefinitions element
CommandUIExtension element
CommandUIHandler element
CommandUIHandlers element
Content element
ContextualGroup element
ContextualTabs element
Controls element
CustomAction element (CustomActionDefinitions)
CustomAction element (ElementDefinitionCollection)
DropDown element
ElementFile element
ElementManifest element
ElementManifests element
Elements element
EnumItem element
EnumItems element
Feature element
Feature element (FeatureTemplateReferences)
FlyoutAnchor element
Gallery element
GalleryButton element
Group element
Groups element
GroupTemplate element
InsertTable element
Label element
MaxSize element
Menu element
MenuSection element
MRUSplitButton element
Properties element (FeatureDefinition)
Properties element (FeatureTemplateReference)
Properties element (ClientWebPartDefinition)
Property element (FeaturePropertyDefinitions)
Property element (PropertyBagDefinition)
Property element (ClientWebPartProperties)
QAT element
Ribbon element
Scale element
Scaling element
Spinner element
SplitButton element
Tab element
Tabs element
TextBox element
ToggleButton element
UpgradeActions element
UrlAction element
VersionRange element
Types (AppHostWebFeatures)
ClientWebPartDefinition complexType
ClientWebPartDefinitionContent complexType
ClientWebPartEnumItem complexType
ClientWebPartEnumItems complexType
ClientWebPartProperties complexType
ClientWebPartProperty complexType
CommandUIDefinitionsType complexType
CommandUIDefinitionType complexType
CommandUIExtensionType complexType
CommandUIHandlersType complexType
CommandUIHandlerType complexType
CustomActionDefinition complexType
CustomActionDefinitions complexType
ElementDefinitionCollection complexType
ElementManifestReference complexType
ElementManifestReferences complexType
FeatureActivationDependencyDefinition complexType
FeatureActivationDependencyDefinitions complexType
FeatureDefinition complexType
FeaturePropertyDefinition complexType
FeaturePropertyDefinitions complexType
FeatureTemplateReference complexType
FeatureTemplateReferences complexType
PropertyBagDefinition complexType
PropertyValueAttributeDefinition complexType
SimplePropertyDefinition complexType
UpgradeActionsDefinition complexType
UrlActionDefinition complexType
VersionRangeDefinition complexType
ClientWebPartDefinitionContentType simpleType
ClientWebPartPropertyType simpleType
CustomActionLocations simpleType
CustomActionRegistrationType simpleType
FeatureScope simpleType
FeatureVersion simpleType
PropertyBagParentTypeDefinition simpleType
PropertyBagType simpleType
UIVersion simpleType
WebPartPersonalizationScope simpleType
AppPartConfigDefinition schema reference
Schema map (AppPartConfigDefinition)
Elements (AppPartConfigDefinition)
AppPartConfig element
Id element
Types (AppPartConfigDefinition)
AppPartConfigDefinition complexType
GUID simpleType
Business connectivity services (BCS) schemas
SolutionManifestDefinitions schema
ContextDefinition element
ContextDefinitionGroup element
ContextDefinitionGroups element
Entities element
Entity element
OfficeItemCustomizations element
PromotedProperty element
SolutionDefinition element
SolutionSettings element
View element
OutlookItemCustomizations element
OfficeItemProperties element
OfficeItemProperty element
FormRegions element
FormRegion element
Picture element
OutlookFolder element
Associations element
Association element
Views element
FolderViewDefinition element
SolutionManifestDeclarativeExtensions schema
Actions element
CodeMethodAction element
ConstantParameter element
ContextActivated element
ContextDeactivated element
ContextEventHandlers element
DeclarativeAssociation element
DeclarativeContextDefinition element
DeclarativeContextDefinitionGroup element
DeclarativeFormRegion element
DeclarativeFormRegions element
DeclarativeSolutionSettings element
ExpressionParameter element
Layout element
Layouts element
Parameters element
Picture element
UrlAction element
LayoutDefinitions schema
ActionName element
ActionNames element
Children element
Container element
CustomProperties element
CustomProperty element
OBPart element
TitleBar element
Subscription schema
Association element
Associations element
FilterValue element
FilterValues element
FilterValues element
Identities element
Identity element
LocalizedDisplayName element
LocalizedDisplayNames element
Properties element
Property element
Queries element
Query element
Subscription element
BDCMetadata schema
AccessControlEntry element
AccessControlList element
Action element
ActionParameter element
ActionParameters element
Actions element
Association element
AssociationGroup element
AssociationGroups element
AssociationReference element
ConvertType element
DefaultValue element
DefaultValues element
DestinationEntity element
Entities element
Entity element
FilterDescriptor element
FilterDescriptors element
Identifier element
Identifiers element
Interpretation element
LobSystem element
LobSystemInstance element
LobSystemInstances element
LobSystems element
LocalizedDisplayName element
LocalizedDisplayNames element
Method element
MethodInstance element
MethodInstances element
Methods element
Model element
NormalizeDateTime element
Parameter element
Parameters element
Properties element
Property element
Proxy element
Right element
SourceEntity element
TypeDescriptor element
TypeDescriptors element
BDCMetadataResource schema
AccessControlEntry element
AccessControlList element
Action element
ActionParameter element
ActionParameters element
Actions element
Association element
AssociationGroup element
AssociationGroups element
Entities element
Entity element
FilterDescriptor element
FilterDescriptors element
Identifier element
Identifiers element
LobSystem element
LobSystemInstance element
LobSystemInstances element
LobSystems element
LocalizedDisplayName element
LocalizedDisplayNames element
Method element
MethodInstance element
MethodInstances element
Methods element
Model element
Parameter element
Parameters element
Properties element
Property element
Right element
TypeDescriptor element
TypeDescriptor element
TypeDescriptors element
Workflow schemas
WorkflowActions4 schema reference
Schema map (Workflow actions)
Elements (Workflow actions)
Action element
ActionBody element
ActionConditions element
Actions element
ActionVariables element
ActivityBody element
ActivitySource element
AssemblyRedirect element
AssemblyRedirects element
Block element
Blocks element
Coercion element
Coercions element
CompositeStep element
CompositeSteps element
Condition element
Conditions element
ContentType element
DataSource element
DataSourceRef element
DataSources element (Action element)
DataSources element (CompositeStep element)
Default element (Conditions element)
Default element (Actions element)
Dictionary element
Evaluation element
Event element
Events element
Field element
FieldBind element
Fields element
Flow element
Flows element
HashtableSource element
Modification element
Modifications element
NestedInitiationFieldNodes element
Option element
Parameter element (parametersType)
Parameter element (coercionParametersType)
Parameters element (Condition element)
Parameters element (Action element)
Parameters element (Coercion element)
Parameters element (Flow element)
Property element
RuleDesigner element (Default element)
RuleDesigner element (Condition element)
RuleDesigner element (Action element)
RuleDesigner element (Flow element)
RuleDesigner element (defaultElementType)
SchemaSource element
VariableType element
VariableTypes element
WorkflowInfo element
Types (Workflow actions)
appliesToTypes simpleType
coercionParametersType complexType
dataSourcesType complexType
dataSourceType complexType
defaultElementType complexType
parametersType complexType
propertiesType complexType
ruleDesignerType complexType
WorkflowActions3 schema reference
Action element
ActionBody element
ActionConditions element
ActionVariables element
ActivitySource element
ContentType element
DataSources element
DataSource element
DataSourceRef element
Default element
Dictionary element
Evaluation element
Field element
FieldBind element
Fields element
HashtableSource element
Modifications element
Modification element
NestedInitiationFieldNodes element
Option element
Parameter element
Parameters element
RuleDesigner element
SchemaSource element
WorkflowActions element
Workflow configuration schema reference
WorkflowConfig element
Template element
Association element
ContentTypes element
ContentType element
Initiation element
Fields element
Parameters element
Parameter element
WorkflowInfo schema reference
.ACTIONS File Example
Action element
ActionBody element
ActionConditions element
ActionVariables element
Actions element
Actions schema reference
ActivitySource element
AssemblyRedirect element
AssemblyRedirects element
Coercion element
Coercions element
Condition element
Conditions element
CompositeSteps element
CompositeStep element
ContentType element
DataSource element
DataSourceRef element
DataSources element
Default element
Default Workflow Actions
Default Workflow Conditions
Dictionary element
Evaluation element
Field element
FieldBind element
Fields element
HashtableSource element
Modification element
Modifications element
NestedInitiationFieldNodes element
Option element
Parameter element
Parameters element
RuleDesigner element
SchemaSource element
VariableType element
VariableTypes element
WorkflowInfo element
SharePoint Features schemas
Client Web Part Definition schema
Content Type Bindings
ContentTypeBinding element
Elements element
Content Type Definitions
ContentType element
DocumentTemplate element
Elements element
FieldRef element
FieldRefs element
Folder element
RemoveFieldRef element
XmlDocument element
XmlDocuments element
Custom Action Definition schema
CommandUIDefinitions element
CommandUIDefinition element
CommandUIExtension element
CommandUIHandlers element
CommandUIHandler element
CustomAction element
CustomActionGroup element
Elements element
HideCustomAction element
UrlAction element
Default Custom Action Locations and IDs
Delegate Controls
Control element
Elements element
Property element
Document Converter
DocumentConverter element
Elements element
Event Registrations
Assembly element
Class element
Data element
Elements element
Filter element
Name element
Receiver element
Receivers element
SequenceNumber element
SolutionId element
Synchronization element
Type element
SourceId element
SourceType element
Feature.xml Files
ActivationDependencies element
ActivationDependency element
AddContentTypeField element
ApplyElementManifests element
CustomUpgradeAction element
ElementFile element
ElementManifest element
ElementManifests element
Feature element
MapFile element
Parameter element
Parameters element
Properties element
Property element
UpgradeActions element
VersionRange element
Feature/Site Template Associations
Elements element
FeatureSiteTemplateAssociation element
Property element
Field Definitions
Elements element
Field element
List Instances
Data element
Elements element
Field element
ListInstance element
Row element
Rows element
DataSource element
Property element
List Template Files
Elements element
ListTemplate element
List Definition (Schema.xml) Files
Modules
AllUsersWebPart element
BinarySerializedWebPart element
Elements element
File element
GUID element
GUIDMap element
Module element
NavBarPage element
Property element
View element
WebPart element
WebPartConnection element
WebPartTransformer element
Property Bag schema
Elements element
PropertyBag element
Property element
Site Definition (Onet.xml) Files
Web Template XML
WebTemplate element
Elements
Workflow Definitions
AssociationCategories element
AssociationData element
Categories element
Elements element
ExtendedStatusColumnValues element
InitiationCategories element
InitiationType element
MetaData element
Modification_GUID_Name element
StatusColumnValue element
StatusPageUrl element
Workflow element (Elements)
Content migration schemas
DeploymentManifest schema
Selected Type Definitions
AnonymousState Simple Type
DefaultItemOpen Simple Type
DraftVisibilityType Simple Type
Guid Simple Type
ListItemDocType Simple Type
SecurityModificationType Simple Type
SPBaseType Simple Type
SPDictionaryEntryAccess Simple Type
SPDictionaryEntryValueType Simple Type
SPEventHostType Simple Type
SPEventReceiverType Simple Type
SPListTemplateType Simple Type
SPModerationStatusType Simple Type
SPObjectType Simple Type
SPViewScope Simple Type
TRUEFALSE Simple Type
Aggregations element (SPView)
Aggregations element (SPWebPart)
Assignment element
Attachment element
Attachments element
CalendarViewStyles element (SPView)
CalendarViewStyles element (SPWebPart)
ContentType element
ContentTypes element
DeletedField element
DeletedFields element
DocumentLibrary element
DocumentTemplate element
EventReceiver element
EventReceivers element (SPFile)
EventReceivers element (SPList)
EventReceivers element (SPListItem)
EventReceivers element (SPWeb)
Feature element
Field element (SPFieldCollection)
Field element (FieldDataCollection)
Field element (DeploymentFieldTemplate)
FieldRef element (SPFieldCollection)
FieldRef element (SPFieldLinkCollection)
Fields element (SPList)
Fields element (SPListItem)
FieldTemplate element
File element (SPGenericObject)
File element (SPFileVersionCollection)
Folder element
Form element
Formats element (SPView)
Formats element (SPWebPart)
Forms element
GroupByFooter element (SPView)
GroupByFooter element (SPWebPart)
GroupByHeader (SPView)
GroupByHeader (SPWebPart)
GroupX element
Link element
Links element (SPFile)
Links element (SPListItem)
List element
ListFormBody element (SPView)
ListFormBody element (SPWebPart)
ListItem element (SPGenericObject)
ListItem element (SPListItemVersionCollection)
ListTemplate element
Module element
PagedClientCallbackRowset element (SPView)
PagedClientCallbackRowset element (SPWebPart)
PagedRecurrenceRowset element (SPView)
PagedRecurrenceRowset element (SPWebPart)
PagedRowset element (SPView)
PagedRowset element (SPWebPart)
Personalization element
Personalizations element
PictureLibrary element
Properties element (SPAttachment)
Properties element (SPFile)
Properties element (SPFolder)
Properties element (SPModule)
Properties element (SPWeb)
Property element
Query element (SPView)
Query element (SPWebPart)
Role element
RoleAssignment element
RoleAssignments element
RoleAssignmentX element
Roles element
RoleX element
RowLimit element (SPView)
RowLimit element (SPWebPart)
RowLimitExceeded element (SPView)
RowLimitExceeded element (SPWebPart)
Script element (SPView)
Script element (SPWebPart)
Site element
SPObject element
SPObjects element
Toolbar element (SPView)
Toolbar element (SPWebPart)
UserX element
Versions element (SPFile)
Versions element (SPListItem)
View element
ViewBidiHeader element (SPView)
ViewBidiHeader element (SPWebPart)
View Body element (SPView)
View Body element (SPWebPart)
ViewData element (SPView)
ViewData element (SPWebPart)
ViewEmpty element (SPView)
ViewEmpty element (SPWebPart)
ViewFields element (SPView)
ViewFields element (SPWebPart)
ViewFooter element (SPView)
ViewFooter element (SPWebPart)
ViewHeader element (SPView)
ViewHeader element (SPWebPart)
Views element
ViewStyle element (SPView)
ViewStyle element (SPWebPart)
Web element
WebPart element
WebParts element
WebStructure element
WebTemplate element
DeploymentExportSettings schema
DeploymentObject element
ExportObjects element
ExportSettings element
Guid Simple Type
SPDeploymentObjectType Simple Type
SPExportMethodType Simple Type
SPIncludeDescendents Simple Type
SPIncludeSecurity Simple Type
SPIncludeVersions Simple Type
DeploymentLookupListMap schema
Guid Simple Type
LookupItem element
LookupItems element
LookupList element
LookupLists element
DeploymentRequirements schema
Requirement element
Requirements element
SPRequirementObjectType Simple Type
DeploymentRootObjectMap schema
Guid Simple Type
RootObject element
RootObjects element
SPDeploymentObjectType Simple Type
DeploymentSystemData schema
Guid Simple Type
ManifestFile element
ManifestFiles element
SchemaVersion element
SPDeploymentObjectType Simple Type
SystemData element
SystemObject element
SystemObjects element
DeploymentUserGroupMap schema
Group element
Groups element
Member element
User element
Users element
UserGroupMap element
DeploymentViewFormsList schema
ViewForm element
ViewFormsList element
Collaborative Application Markup Language (CAML) schemas
Introduction to Collaborative Application Markup Language (CAML)
Major CAML Files
Data-defining elements
HTML-rendering elements
Deprecated CAML elements
Query schema
And element
BeginsWith element
Contains element
DateRangesOverlap element
Eq element
FieldRef element
Geq element
GroupBy element
Gt element
In element
Includes element
IsNotNull element
IsNull element
Leq element
ListProperty element
Lt element
Membership element
Month element
Neq element
NotIncludes element
Now element
Or element
OrderBy element
Today element
UserID element
Value element
Values element
Where element
XML element
View schema
Global attributes for HTML-rendering elements
A- F
Batch element
Case element
Column element
Column2 element
ContentTypes element
Counter element
CurrentRights element
Default element
Else element
Expr element
Expr1 element
Expr2 element
Field element
FieldPrefix element
FieldProperty element
Fields element
FieldSortParams element
FieldSwitch element
FilterLink element
ForEach element
G- L
GetFileExtension element
GetVar element
HTML element
HttpHost element
HttpPath element
HttpVDir element
ID element
Identity element
IfEqual element
IfHasRights element
IfNeg element
IfNew element
IfSubString element
Join element
Joins element
Length element
Limit element
List element
ListProperty element
ListUrl element
ListUrlDir element
LookupColumn element
M-Z
MapToAll element
MapToContentType element
MapToControl element
MapToIcon element
MeetingProperty element
Method element
More element
PageUrl element
ProjectProperty element
ProjectedFields element
Property element
RightsChoices element
RightsGroup element
ScriptQuote element
SelectionOptions element
ServerProperty element
SetList element
SetVar element
Switch element
Text element
Then element
ThreadStamp element
URL element
UrlBaseName element
UrlDirName element
UserID element
WebQueryInfo element
List schema
A- K
Aggregations element
AllUsersWebPart element
ArrayOfProperty element
CHOICE element
CHOICES element
ContentTypeRef element
ContentTypes element
Customization element
Default element (Field)
Default element (Form)
DefaultDescription element
DefaultFormula element
DefaultFormulaValue element
DisplayBidiPattern element
DisplayPattern element
DocumentLibraryTemplate element
Field element
FieldRef element
FieldRefs element
Fields element
Filter element
Folder element
Form element
Forms element
Formula element
FormulaDisplayNames element
GroupByFooter element
GroupByHeader element
L- Z
List element
ListFormBody element
ListFormButtons element
ListFormClosing element
ListFormOpening element
MAPPING element
MAPPINGS element
MetaData element
Method element
PagedClientCallbackRowset element
PagedRecurrenceRowset element
PagedRowset element
ParameterBinding element
ParameterBindings element
Property element
Query element
RowLimit element
RowLimitExceeded element
Toolbar element
Validation element
View element
ViewBidiHeader element
ViewBody element
ViewData element
ViewEmpty element
ViewFields element
ViewFooter element
ViewHeader element
Views element
ViewStyle element
WebParts element
XslLink element
Site schema
A- L
AllUsersWebPart element
BaseType element
BaseTypes element
BinarySerializedWebPart element
Components element
Configuration element
Configurations element
Data element
DocumentTemplate element
DocumentTemplateFile element
DocumentTemplateFiles element
DocumentTemplates element
ExecuteUrl element
ExternalSecurityProvider element
Feature element
Field element
File element
FileDialogPostProcessor element
GUID element
GUIDMap element
List element
Lists element
ListTemplate element
ListTemplates element
M-Z
MetaData element
Module element
Modules element
NavBar element
NavBarLink element
NavBarPage element (Module)
NavBarPage element (NavBar)
NavBars element
Project element
Properties element
Property element (Feature)
Property element (Module)
Row element
Rows element
ServerEmailFooter element
SiteFeatures element
Template element
Templates element
View element
WebFeatures element
WebPart element
WebPartConnection element
WebPartTransformer element
Site Deletion Confirmation schema
AutoDeleteBody element
AutoDeleteSubject element
AutoDeleteWarning element
Confirmation element
ConfirmationBody element
ConfirmationSubject element
Email element
Regional Settings schema
Bias element
Currencies element
Currency element
Date element
Day element
DaylightTime element
DayOfWeek element
History element
Hour element
Language element
Languages element
Locale element
Locales element
Month element
RegionalSettings element
StandardTime element
TimeZone element
TimeZones element
Document Icons schema
ByExtension element
ByProgID element
DocIcons element
Mapping element
General schema
A- H
Attachments element
CategoryBot element
CrossProjectLink element
DataFormatLCID element
Discussions element
Escape element
FieldFilterImageURL element
FieldFilterOptions element
FieldSortImageURL element
Format element
FormatDef element
Formats element
GetProgID element
GlobalLists element
GUID element
HTMLBase element
HtmlTrInfo element
I -Z
IfOld element
ImagesPath element
InForm element
IsPrivilegedUser element
ListForm element
LocaleInfo element
Mapping element
MapToText element
OkToVote element
PresenceEnabled element
QuotedXML element
ReadSecurity element
Recurrence element
SchemaSecurity element
Script element
Security element
Site element
TodayISO element
UserEmail element
ViewStyles element
Week element
WriteSecurity element
SharePoint search settings portability schemas
SPS15XSDSearchSet1
Schema map (SPS15XSDSearchSet1)
Elements (SPS15XSDSearchSet1)
Active element
ArrayOfSource element
AuthInfo element
BuiltIn element
ConnectionTimeout element
ConnectionUrlTemplate element
CreatedDate element
Description element
HasPermissionToReadAuthInfo element
Id element
IndexOffset element
LastModifiedDate element
MaximumResponseLength element
Name element
Owner element
ProviderId element
QueryTransform element
Source element (ArrayOfSource)
Source element
Types (SPS15XSDSearchSet1)
ArrayOfSource complexType
Source complexType
SPS15XSDSearchSet2
Schema map (SPS15XSDSearchSet2)
Elements (SPS15XSDSearchSet2)
_AuthSchemeName element
_AuthSubmissionMethod element
_AuthSubmissionPath element
_Cookies element
_ErrorPageUrl element (CookieAuthData)
_ErrorPageUrl element (FormsAuthCredentials)
_nameValuePairs element
_NameValuePairs element
_SecurableNameValuePairs element
_SsoAppId element
_UserName element
AccountAuthCredentials element
Aliases element
AliasesOverridden element
AliasInfo element
AliasInfoCollection element
Anchoring element
ArrayOfCrawledPropertyInfo element
ArrayOfManagedPropertyInfo element
ArrayOfPropertyRule element
ArrayOfResultItemType element
AuthenticationData element
AuthenticationInformation element
AuthenticationType element
BaseInfo element
BaseInfoCollectionOfAliasInfoTzWWwPjw element
BaseInfoCollectionOfCrawledPropertyInfoTzWWwPjw element
BaseInfoCollectionOfManagedPropertyInfoTzWWwPjw element
BaseInfoCollectionOfMappingInfoTzWWwPjw element
BaseInfoCollectionOfOverrideInfoTzWWwPjw element
BuiltIn element
CategoryName element
CompleteMatching element
Context element
CookieAuthData element
CrawledPropertyInfo element
CrawledPropertyInfo element (ArrayOfCrawledPropertyInfo)
CrawledPropertyInfoCollection element
CrawledPropertyName element
CrawledPropset element
CutoffMaxBuckets element
Data element
DatabaseId element
DeleteDisallowed element
Description element
DescriptionLSID element
Dictionary element (AliasInfo)
Dictionary element (CrawledPropertyInfo)
Dictionary element (ManagedPropertyInfo)
Dictionary element (MappingInfo)
Dictionary element (OverrideInfo)
DisableInheritance element
DisplayProperties element
DisplayTemplateUrl element
Divisor element
EnabledForScoping element
EntityExtractorBitMap element
ExtraProperties element
FederationAuthType element
FormsAuthCredentials element
FullTextIndex element
HasMultipleValues element
ID element
IndexOptions element
InternalID element
Intervals element
IsDeleted element
IsFunction element
IsMappedToContents element
IsNameEnum element
IsQuoted element
IsReadOnly element
JoinedByOr element
LastItemName element (AliasInfo)
LastItemName element (CrawledProperty)
LastItemName element (ManagedPropertyInfo)
LastItemName element (MappingInfo)
LastItemName element (OverrideInfo)
LastModifiedDate element
ManagedDataType element
ManagedPid element (AliasInfo)
ManagedPid element (MappingInfo)
ManagedPid element (OverrideInfo)
ManagedPropertyInfo element (ArrayOfManagedPropertyInfo)
ManagedPropertyInfo element
ManagedPropertyInfoCollection element
ManagedType element
MappedCrawledProperties element
MappedManagedProperties element
MappingDisallowed element
MappingInfo element
MappingInfoCollection element
MappingOrder element
MappingsOverridden element
Name element (ResultItemType)
Name element (PropertyRuleOperator)
Name element (BaseInfo)
NameLSID element
OptimizeForFrequentUse element
OverrideInfo element
OverrideInfoCollection element
Owner element
Password element
Pid element
PropertyName element
PropertyOperator element
PropertyRule element (ArrayOfPropertyRule)
PropertyRule element
PropertyRuleCollection element
PropertyRuleOperator element
PropertyRules element
PropertyValues element
Propset element
Queryable element
Refinable element
RefinerAnchoring element
RefinerConfiguration element (ManagedPropertyInfo)
RefinerConfiguration element
RefinerType element
RemoveDuplicates element
Representation element
Resolution element
RespectPriority element
ResultItemType element (ArrayOfResultItemType)
ResultItemType element
Retrievable element
RuleNameLSID element
RulePriority element
Rules element
SafeForAnonymous element
Samples element
SchemaID element (AliasInfo)
SchemaID element (CrawledPropertyInfo)
SchemaID element (MappingInfo)
SchemaID element (OverrideInfo)
Searchable element
SearchObjectOwner element
SecurableAuthData element
SerializableSecurableNameValuePairs element
Sortable element
SortableType element (ManagedPropertyInfo)
SortableType element
SourceID element
SPFarmId element
SPSiteId element
SPSiteSubscriptionId element
SPWebId element
SsoAuthData element
TokenNormalization element (ManagedPropertyInfo)
TokenNormalization element (OverrideInfo)
TotalCount element
Type element
UpdateGroup element
Types (SPS15XSDSearchSet2)
AccountAuthCredentials complexType
AliasInfo complexType
AliasInfoCollection complexType
ArrayOfCrawledPropertyInfo complexType
ArrayOfManagedPropertyInfo complexType
ArrayOfPropertyRule complexType
ArrayOfResultItemType complexType
AuthenticationData complexType
AuthenticationInformation complexType
BaseInfo complexType
BaseInfoCollectionOfAliasInfoTzWWwPjw complexType
BaseInfoCollectionOfCrawledPropertyInfoTzWWwPjw complexType
BaseInfoCollectionOfManagedPropertyInfoTzWWwPjw complexType
BaseInfoCollectionOfMappingInfoTzWWwPjw complexType
BaseInfoCollectionOfOverrideInfoTzWWwPjw complexType
CookieAuthData complexType
CrawledPropertyInfo complexType
CrawledPropertyInfoCollection complexType
FormsAuthCredentials complexType
ManagedPropertyInfo complexType
ManagedPropertyInfoCollection complexType
MappingInfo complexType
MappingInfoCollection complexType
OverrideInfo complexType
OverrideInfoCollection complexType
PropertyRule complexType
PropertyRuleCollection complexType
PropertyRuleOperator complexType
RefinerConfiguration complexType
ResultItemType complexType
SearchObjectOwner complexType
SecurableAuthData complexType
SsoAuthData complexType
FederationAuthType simpleType
ManagedDataType simpleType
RefinerAnchoring simpleType
RefinerType simpleType
SortableType simpleType
SPS15XSDSearchSet3
Schema map (SPS15XSDSearchSet3)
Elements (SPS15XSDSearchSet3)
Aliases element
ArrayOfSearchQueryConfigurationSettings element
BestBets element
CategoriesAndCrawledProperties element
CrawledProperties element
DefaultSourceId element
DefaultSourceIdSet element
DeployToParent element
DisableInheritanceOnImport element
ManagedProperties element
Mappings element
Overrides element
QueryRuleGroups element
QueryRules element
RankingModels element
ResultTypes element
SearchConfigurationSettings element
SearchQueryConfigurationSettings element (SearchConfigurationSettings)
SearchQueryConfigurationSettings element
(ArrayOfSearchQueryConfigurationSettings)
SearchQueryConfigurationSettings element
SearchRankingModelConfigurationSettings element
(SearchConfigurationSettings)
SearchRankingModelConfigurationSettings element
SearchSchemaConfigurationSettings element (SearchConfigurationSettings)
SearchSchemaConfigurationSettings element
Sources element
UserSegments element
Types (SPS15XSDSearchSet3)
ArrayOfSearchQueryConfigurationSettings complexType
SearchConfigurationSettings complexType
SearchQueryConfigurationSettings complexType
SearchRankingModelConfigurationSettings complexType
SearchSchemaConfigurationSettings complexType
SPS15XSDSearchSet4
Schema map (SPS15XSDSearchSet4)
Elements (SPS15XSDSearchSet4)
BoundVariableOrigin element
GroupProcessingDirective element
LicenseType element
MatchingOptions element
QueryActionEnableOnClickThroughOptions element
QueryTransformParentType element
Types (SPS15XSDSearchSet4)
BoundVariableOrigin simpleType
GroupProcessingDirective simpleType
LicenseType simpleType
MatchingOptions simpleType
QueryActionEnableOnClickThroughOptions simpleType
QueryTransformParentType simpleType
SPS15XSDSearchSet5
Schema map (SPS15XSDSearchSet5)
Elements (SPS15XSDSearchSet5)
AlertChangeType element
ArrayOfReorderingRule element
Boost element
KeywordInclusion element
MatchType element
MatchValue element
QueryAuthenticationType element
QueryHint element
ReorderingRule element (ArrayOfReorderingRule)
ReorderingRule element
ReorderingRuleMatchType element
ResubmitFlag element
ResultType element
SearchProvider element
SimilarType element
SortDirection element
SpellcheckMode element
Types (SPS15XSDSearchSet5)
AlertChangeType simpleType
KeywordInclusion simpleType
QueryAuthenticationType simpleType
QueryHint simpleType
ReorderingRuleMatchType simpleType
ResubmitFlag simpleType
ResultType simpleType
SearchProvider simpleType
SimilarType simpleType
SortDirection simpleType
SpellcheckMode simpleType
ArrayOfReorderingRule complexType
ReorderingRule complexType
SPS15XSDSearchSet6
Schema map (SPS15XSDSearchSet6)
Elements (SPS15XSDSearchSet6)
LocStringId element
Types (SPS15XSDSearchSet6)
LocStringId simpleType
Other SharePoint schemas
Field Types schema
FieldTypes element
FieldType element
Field element
PropertySchema element
Fields element
Field element
Default element
RenderPattern element
AlertTemplates schema
AlertTemplate element
AlertTemplates element
Digest element
DigestNotificationExcludedFields element
EventTypes element
Fields element
FilterDefinition element
Filters element
Footer element
Format element
FriendlyName element
Header element
HeaderFields element
HeaderFieldsFooter element
HeaderFieldsHeader element
Immediate element
ImmediateNotificationExcludedFields element
NotificationHandlerAssembly element
NotificationHandlerClassName element
Properties element
Query element
RowFields element
RowFooter element
RowHeader element
ShortName element
Subject element
UpdateHandlerAssembly element
UpdateHandlerClassName element
Mobile Document Viewer schema
MobileDocViewers element
MobileDocViewer element
BrowserCondition element
Override element
Server Ribbon schema
Button element
CheckBox element
ColorPicker element
Colors element
Color element
ComboBox element
CommandUI element
ContextualGroup element
ContextualTabs element
ControlRef element
Controls element (CommandUIDefinition)
Controls element (Group)
Controls element (MenuSection)
DropDown element
FlyoutAnchor element
Gallery element
GalleryButton element (Gallery)
GalleryButton element (Group)
Groups element
Group element
GroupTemplate element
InsertTable element
Jewel element
Label element
Layout element
MaxSize element
Menu element
MenuSection element
MRUSplitButton element
OverflowSection element
OverflowArea element
QAT element
Ribbon element
RibbonTemplates element
Row element
Scale element
Scaling element
Section element
Spinner element
SplitButton element
Strip element
Tabs element
Tab element
Templates element
TextBox element
ToggleButton element
Unit element
UnitAbbreviation element
Solution schema
ActivationDependencies element
ActivationDependency element
App_GlobalResourceFile element
ApplicationResourceFile element
ApplicationResourceFiles element
Assemblies element (Assemblies)
Assemblies element (CodeAccessSecurity)
Assembly element (Assemblies)
Assembly element (CodeAccessSecurity)
BindingRedirect element
BindingRedirects element
ClassResource element
ClassResources element
CodeAccessSecurity element
DwpFile element
DwpFiles element
FeatureManifest element
FeatureManifests element
IPermission element
PermissionSet element
PolicyItem element
Resource element
Resources element
RootFile element
RootFiles element
SafeControl element
SafeControls element
SiteDefinitionManifest element
SiteDefinitionManifests element
Solution element
TemplateFile element
TemplateFiles element
WebTempFile element
SPMetal Parameters schema
Column element
ContentType element
ExcludeColumn element
ExcludeContentType element
ExcludeList element
ExcludeOtherColumns element
ExcludeOtherContentTypes element
ExcludeOtherLists element
IncludeHiddenColumns element
IncludeHiddenContentTypes element
IncludeHiddenLists element
List element
Web element
Upgrade Definition schema
AppliedSiteFeatures element
AppliedWebFeatures element
Config element
Feature element
File element
Files element
List element
Lists element
WebTemplate element
dsQueryResponse schema
dsQueryResponse element
Rows element
Row element
Community
Contribute
Contribute
Open source projects
GitHub repositories
Submit issues
Submit feature requests
SharePoint developer videos on YouTube
Office development on Twitter
SharePoint developer forum
Tutorials
Build SharePoint Framework solutions, apps, add-ins, and solutions for SharePoint for your enterprise or customer needs.

Setup

Get started

Community

SharePoint Dev Blog

Videos

Samples

Ideas

Issues

Forum

SharePoint Framework
Overview of the SharePoint Framework
Set up your development environment
Build your first client-side web part

SharePoint REST service


Get to know the SharePoint REST service
Complete basic operations
SharePoint in Microsoft Graph

SharePoint webhooks
Overview of SharePoint webhooks
Get started with SharePoint webhooks
SharePoint list webhooks

SharePoint Add-ins
Overview of SharePoint Add-ins
Provider-hosted add-ins
SharePoint-hosted add-ins

SharePoint development overview


Programming models for SharePoint
Use the Office 365 content delivery network
Use the site collection app catalog
Refer to the SharePoint glossary

Solution guidance
Modernize your classic SharePoint sites
Customizing modern experiences
Building SharePoint Online portals

"Sharing is caring!"
Overview of the SharePoint Framework
4/20/2018 • 5 minutes to read Edit Online

The SharePoint Framework (SPFx) is a page and web part model that provides full support for client-side SharePoint development, easy integration with SharePoint data, and support for
open source tooling. With the SharePoint Framework, you can use modern web technologies and tools in your preferred development environment to build productive experiences and apps
that are responsive and mobile-ready from day one. The SharePoint Framework works for SharePoint Online and soon also for on-premises (SharePoint 2016 Feature Pack 2).
Key features of the SharePoint Framework include the following:
It runs in the context of the current user and connection in the browser. There are no iFrames for the customization (JavaScript is embedded directly to the page).
The controls are rendered in the normal page DOM.
The controls are responsive and accessible by nature.
It enables the developer to access the lifecycle in addition to render, load, serialize and deserialize, configuration changes, and more.
It is framework-agnostic. You can use any JavaScript framework that you like: React, Handlebars, Knockout, Angular, and more.
The toolchain is based on common open source client development tools such as npm, TypeScript, Yeoman, webpack, and gulp.
Performance is reliable.
End users can use SPFx client-side solutions that are approved by the tenant administrators (or their delegates) on all sites, including self-service team, group, or personal sites.
SPFx web parts can be added to both classic and modern pages.
The runtime model improves on the Script Editor web part. It includes a robust client API, an HttpClient object that handles authentication to SharePoint and Office 365, contextual
information, easy property definition and configuration, and more.
If you work primarily with C#, you want to learn more about client-side JavaScript development. Most of your existing JavaScript knowledge related to SharePoint, however, is completely
transferable, as the data models have not changed, and you’ll use the same REST services or JavaScript Object Model (JSOM), depending on your requirements. If you are a C# developer,
TypeScript is a nice transition into the JavaScript world. The choice of IDE is up to you. Many developers like to use the cross-platform IDE Visual Studio Code. Many developers also use
products like Sublime and ATOM. Use what works best for you.

Why the SharePoint Framework?


SharePoint was launched as an on-premises product in 2001. Over time, a large developer community has extended and shaped it in many ways. For the most part, the developer
community followed the same patterns and practices that the SharePoint product development team used, including web parts, SharePoint feature XML, and more. Many features were
written in C#, compiled to DLLs, and deployed to on-premises farms.
That architecture worked well in environments with only one enterprise, but it didn’t scale to the cloud, where multiple tenants run side-by-side. As a result, we introduced two alternative
models: client-side JavaScript injection, and SharePoint Add-ins. Both of these solutions have pros and cons.

JavaScript injection
One of the most popular web parts in SharePoint Online is the Script Editor. You can paste JavaScript into the Script Editor web part and have that JavaScript execute when the page renders.
It’s simple and rudimentary, but effective. It runs in the same browser context as the page, and is in the same DOM, so it can interact with other controls on the page. It is also relatively
performant, and simple to use.
There are a few downsides to this approach, however. First, while you can package your solution so that end users can drop the control onto the page, you can't easily provide configuration
options. Also, the end user can edit the page and modify the script, which can break the web part. Another big problem is that the Script Editor web part is not marked as "Safe For
Scripting". Most self-service site collections (my-sites, team sites, group sites) have a feature known as "NoScript" enabled. Technically, it is the removal of the Add/Customize Pages (ACP)
permission in SharePoint. This means that the Script Editor web part will be blocked from executing on these sites.

SharePoint Add-in model


The current option for solutions that run in NoScript sites is the add-in/app-part model. This implementation creates an iFrame where the actual experience resides and executes. The
advantage is that because it's external to the system and has no access to the current DOM/connection, it's easier for information workers to trust and deploy. End users can install add-ins on
NoScript sites.
There are some downsides to this approach as well. First, they run in an iFrame. iFrames are slower than the Script Editor web part, because it requires a new request to another page. The
page has to go through authentication and authorization, make its own calls to get SharePoint data, load various JavaScript libraries, and more. A Script Editor web part might typically take,
for example, 100 milliseconds to load and render, while an app part might take 2 seconds or more. Additionally, the iFrame boundary makes it more difficult to create responsive designs
and inherit CSS and theming information. iFrames do have stronger security, which can be useful for you (your page is inaccessible by other controls on the page) and for the end user (the
control has no access to their connection to Office 365).

SharePoint Framework
Historically, we created web parts as full trust C# assemblies that were installed on the cloud servers. However, current development models for the most part involve JavaScript running in a
browser making REST API calls to the SharePoint and Office 365 back-end workloads. C# assemblies don’t work in this world. We needed a new development model. The SharePoint
Framework is the next evolution in SharePoint development.

What's next?
SharePoint Framework web parts and extensions have now reached General Availability (GA). We continue to provide updates and refinements over time based on your feedback and
experiences. For any additional SharePoint Framework capabilities that are first launched in preview mode, you might experience occasional breaking changes around API names, flows, and
more. Future updates to the SharePoint Framework will be backward-compatible so that your solutions continue to work.

SharePoint Framework License


The SharePoint Framework components are licensed under this Microsoft EUL A.

Questions?
If you have any questions, post them on SharePoint StackExchange. Tag your questions and comments with #spfx, #spfx-webparts, and #spfx-tooling.
You can also post issues, questions, or feedback about the docs or the SharePoint Framework in our GitHub repo.

See also
Overview of SharePoint client-side web parts
Overview of SharePoint Framework Extensions
SharePoint development
SharePoint glossary
Set up your Office 365 tenant
3/26/2018 • 2 minutes to read Edit Online

To build and deploy client-side web parts using the SharePoint Framework, you need an Office 365 tenant.
If you already have an Office 365 tenant, see the section Create app catalog site.
If you don't have one, you can get an Office 365 developer subscription when you join the Office 365 Developer Program. See the Office 365 Developer Program documentation for step-
by-step instructions about how to join the Office 365 Developer Program and sign up and configure your subscription.
NOTE

Make sure that you are signed out of any existing Office 365 tenants before you sign up.

Create app catalog site


You need an app catalog to upload and deploy web parts. If you've already set up an app catalog, see create a new developer site collection.

To create an app catalog site


1. Go to the SharePoint Admin Center by entering the following URL in your browser. Replace yourtenantprefix with your Office 365 tenant prefix.

https://yourtenantprefix-admin.sharepoint.com

2. In the left sidebar, select the apps menu item, and then select app catalog.
3. Select OK to create a new app catalog site.
4. On the next page, enter the following details:
Title: Enter app catalog.
Web Site Address suffix: Enter your preferred suffix for the app catalog; for example: apps.
Administrator: Enter your username, and then select the resolve button to resolve the username.
5. Select OK to create the app catalog site.
SharePoint creates the app catalog site, and you are able to see its progress in the SharePoint admin center.

Create a new developer site collection


You also need a site collection and a site for your testing. You can create a new site collection by using any of the available templates. You may choose to use developer site collection, but
that does not really add additional value because workbench and basic testing can be performed under any site.

To create a new developer site collection


1. Go to the SharePoint Admin Center by entering the following URL in your browser. Replace yourtenantprefix with your Office 365 tenant prefix.

https://yourtenantprefix-admin.sharepoint.com

2. On the SharePoint ribbon, select New > Private Site Collection.


3. In the dialog box, enter the following details:
Title: Enter a title for your developer site collection; for example: Developer Site.
Web Site Address suffix: Enter a suffix for your developer site collection; for example: dev.
Template Selection: Select Developer Site as the site collection template.
Administrator: Enter your username, and then select the resolve button to resolve the username.
4. Select OK to create the site collection.
SharePoint creates the developer site and you are able to see its progress in the SharePoint admin center. After the site is created, you can browse to your developer site collection.

SharePoint Workbench
SharePoint Workbench is a developer design surface that enables you to quickly preview and test web parts without deploying them in SharePoint. SharePoint Framework developer
toolchain contains a version of the Workbench that works locally and helps you quickly test and validate solutions that you are building. It is also hosted in your tenancy to preview and test
your local web parts in development. You can access the SharePoint Workbench from any SharePoint site in your tenancy by browsing to the following URL:

https://your-sharepoint-site/_layouts/workbench.aspx

Next steps
Now that you have configured your SharePoint tenant, set up your development environment to build client-side web parts.
Set up your Office 365 tenant
3/26/2018 • 2 minutes to read Edit Online

To build and deploy client-side web parts using the SharePoint Framework, you need an Office 365 tenant.
If you already have an Office 365 tenant, see the section Create app catalog site.
If you don't have one, you can get an Office 365 developer subscription when you join the Office 365 Developer Program. See the Office 365 Developer Program documentation for step-
by-step instructions about how to join the Office 365 Developer Program and sign up and configure your subscription.
NOTE

Make sure that you are signed out of any existing Office 365 tenants before you sign up.

Create app catalog site


You need an app catalog to upload and deploy web parts. If you've already set up an app catalog, see create a new developer site collection.

To create an app catalog site


1. Go to the SharePoint Admin Center by entering the following URL in your browser. Replace yourtenantprefix with your Office 365 tenant prefix.

https://yourtenantprefix-admin.sharepoint.com

2. In the left sidebar, select the apps menu item, and then select app catalog.
3. Select OK to create a new app catalog site.
4. On the next page, enter the following details:
Title: Enter app catalog.
Web Site Address suffix: Enter your preferred suffix for the app catalog; for example: apps.
Administrator: Enter your username, and then select the resolve button to resolve the username.
5. Select OK to create the app catalog site.
SharePoint creates the app catalog site, and you are able to see its progress in the SharePoint admin center.

Create a new developer site collection


You also need a site collection and a site for your testing. You can create a new site collection by using any of the available templates. You may choose to use developer site collection, but
that does not really add additional value because workbench and basic testing can be performed under any site.

To create a new developer site collection


1. Go to the SharePoint Admin Center by entering the following URL in your browser. Replace yourtenantprefix with your Office 365 tenant prefix.

https://yourtenantprefix-admin.sharepoint.com

2. On the SharePoint ribbon, select New > Private Site Collection.


3. In the dialog box, enter the following details:
Title: Enter a title for your developer site collection; for example: Developer Site.
Web Site Address suffix: Enter a suffix for your developer site collection; for example: dev.
Template Selection: Select Developer Site as the site collection template.
Administrator: Enter your username, and then select the resolve button to resolve the username.
4. Select OK to create the site collection.
SharePoint creates the developer site and you are able to see its progress in the SharePoint admin center. After the site is created, you can browse to your developer site collection.

SharePoint Workbench
SharePoint Workbench is a developer design surface that enables you to quickly preview and test web parts without deploying them in SharePoint. SharePoint Framework developer
toolchain contains a version of the Workbench that works locally and helps you quickly test and validate solutions that you are building. It is also hosted in your tenancy to preview and test
your local web parts in development. You can access the SharePoint Workbench from any SharePoint site in your tenancy by browsing to the following URL:

https://your-sharepoint-site/_layouts/workbench.aspx

Next steps
Now that you have configured your SharePoint tenant, set up your development environment to build client-side web parts.
Set up your SharePoint Framework development environment
6/18/2018 • 2 minutes to read Edit Online

You can use Visual Studio or your own custom development environment to build SharePoint Framework solutions. You can use a Mac, PC, or Linux.
NOTE

Before following the steps in this article, be sure to Set up your Office 365 tenant.
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/-tXf8gxjmOI

Install developer tools


Install NodeJS
Install NodeJS LTS version.
If you are in Windows, you can use the msi installers in this link for the easiest way to set up NodeJS.
If you have NodeJS already installed, check that you have the latest version by using node -v . It should return the current LTS version.
If you are using a Mac, we recommend that you use homebrew to install and manage NodeJS.
NOTE

Current LTS version of NodeJS is 8.11.3. Notice that 9.x versions are currently not supported with SharePoint Framework development.

Install a code editor


You can use any code editor or IDE that supports client-side development to build your web part, such as:
Visual Studio Code
Atom
Webstorm
The steps and examples in this documentation use Visual Studio Code, but you can use any editor of your choice.

If you are using Ubuntu


You need to install compiler tools by using the following command:

sudo apt-get install build-essential

If you are using fedora


You need to install compiler tools by using the following command:

sudo yum install make automake gcc gcc-c++ kernel-devel

Install Yeoman and gulp


Yeoman helps you kick-start new projects, and prescribes best practices and tools to help you stay productive. SharePoint client-side development tools include a Yeoman generator for
creating new web parts. The generator provides common build tools, common boilerplate code, and a common playground website to host web parts for testing.
Enter the following command to install Yeoman and gulp:

npm install -g yo gulp

Install Yeoman SharePoint generator


The Yeoman SharePoint web part generator helps you quickly create a SharePoint client-side solution project with the right toolchain and project structure.
To install the SharePoint Framework Yeoman generator globally, enter the following command:

npm install -g @microsoft/generator-sharepoint

If you need to switch between the different projects created by using different versions of the SharePoint Framework Yeoman generator, you can install the generator locally as a
development dependency in the project folder by executing the following command:

npm install @microsoft/generator-sharepoint --save-dev

For more information about the Yeoman SharePoint generator, see Scaffold projects by using Yeoman SharePoint generator.

Optional tools
Following are some tools that might come in handy as well:
Fiddler
Postman plug-in for Chrome
Cmder for Windows
Oh My Zsh for Mac
Git source control tools

Next steps
You are now ready to build your first client-side web part!
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at sp-dev-docs repository. Thanks for your input in
advance.
Overview of SharePoint client-side web parts
3/26/2018 • 2 minutes to read Edit Online

SharePoint client-side web parts are controls that appear inside a SharePoint page but run locally in the browser. They're the building blocks of pages that appear on a SharePoint site.
You can build client-side web parts using modern script development tools and the SharePoint workbench (a development test surface), and you can deploy your client-side web parts to
modern pages and classic web part pages in Office 365 tenants.
In addition to plain JavaScript projects, you can build web parts alongside common scripting frameworks, such as AngularJS and React. For example, you can use React along with
components from Office UI Fabric React to quickly create experiences based on the same components used in Office 365.
For more information, see the Getting started, Basics, and Concepts sections (in the TOC).

See also
Overview of the SharePoint Framework
SharePoint Framework development tools and libraries
Build your first SharePoint client-side web part (Hello World part 1)
6/18/2018 • 11 minutes to read Edit Online

Client-side web parts are client-side components that run inside the context of a SharePoint page. Client-side web parts can be deployed to SharePoint Online, and you can also use modern
JavaScript tools and libraries to build them.
Client-side web parts support:
Building with HTML and JavaScript.
Both SharePoint Online and on-premises environments.
NOTE

Before following the steps in this article, be sure to Set up your development environment.
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/YqUIX2pMUzg

Create a new web part project


To create a new web part project
1. Create a new project directory in your favorite location.

md helloworld-webpart

2. Go to the project directory.

cd helloworld-webpart

3. Create a new HelloWorld web part by running the Yeoman SharePoint Generator.

yo @microsoft/sharepoint

4. When prompted:
Accept the default helloworld-webpart as your solution name, and then select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder for where to place the files.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select WebPart as the client-side component type to be created.
5. The next set of prompts ask for specific information about your web part:
Accept the default HelloWorld as your web part name, and then select Enter.
Accept the default HelloWorld description as your web part description, and then select Enter.
Accept the default No javascript web framework as the framework you would like to use, and then select Enter.
At this point, Yeoman installs the required dependencies and scaffolds the solution files along with the HelloWorld web part. This might take a few minutes.
When the scaffold is complete, you should see the following message indicating a successful scaffold.

For information about troubleshooting any errors, see Known issues.

Using your favorite Code Editor


Because the SharePoint client-side solution is HTML/TypeScript based, you can use any code editor that supports client-side development to build your web part, such as:
Visual Studio Code
Atom
Webstorm
SharePoint Framework documentation uses Visual Studio code in the steps and examples. Visual Studio Code is a lightweight but powerful source code editor from Microsoft that runs on
your desktop and is available for Windows, Mac, and Linux. It comes with built-in support for JavaScript, TypeScript, and Node.js, and has a rich ecosystem of extensions for other languages
(such as C++, C#, Python, PHP) and runtimes.

Preview the web part


To preview your web part, build and run it on a local web server. The client-side toolchain uses HTTPS endpoint by default. However, because a default certificate is not configured for the
local dev environment, your browser reports a certificate error. The SPFx toolchain comes with a developer certificate that you can install for building web parts.

To install the developer certificate and preview your web part


1. Switch to your console, ensure that you are still in the helloworld-webpart directory, and then enter the following command:

gulp trust-dev-cert

2. Now that we have installed the developer certificate, enter the following command in the console to build and preview your web part:

gulp serve

This command executes a series of gulp tasks to create a local, node-based HTTPS server on localhost:4321 and launches your default browser to preview web parts from your local dev
environment.
SharePoint client-side development tools use gulp as the task runner to handle build process tasks such as:
Bundling and minifying JavaScript and CSS files.
Running tools to call the bundling and minification tasks before each build.
Compiling SASS files to CSS.
Compiling TypeScript files to JavaScript.
Visual Studio Code provides built-in support for gulp and other task runners. Select Ctrl+Shift+B on Windows or Cmd+Shift+B on Mac to debug and preview your web part.
SharePoint Workbench is a developer design surface that enables you to quickly preview and test web parts without deploying them in SharePoint. SharePoint Workbench includes the
client-side page and the client-side canvas in which you can add, delete, and test your web parts in development.

To use SharePoint Workbench to preview and test your web part


1. To add the HelloWorld web part, select the add icon (this icon appears when you mouse hovers over a section as shown in the previous image). This opens the toolbox where you can
see a list of web parts available for you to add. The list includes the HelloWorld web part as well other web parts available locally in your development environment.

2. Select HelloWorld to add the web part to the page.


Congratulations! You have just added your first client-side web part to a client-side page.
3. Select the pencil icon on the far left of the web part to reveal the web part property pane.

The property pane is where you can define properties to customize your web part. The property pane is client-side driven and provides a consistent design across SharePoint.
4. Modify the text in the Description text box to Client-side web parts are awesome!
Notice how the text in the web part also changes as you type.
One of the new capabilities available to the property pane is to configure its update behavior, which can be set to reactive or non-reactive. By default, the update behavior is reactive and
enables you to see the changes as you edit the properties. The changes are saved instantly when the behavior is reactive.

Web part project structure


To use Visual Studio Code to explore the web part project structure
1. In the console, break the processing by selecting Ctrl+C (in Windows).
2. Enter the following command to open the web part project in Visual Studio Code (or use your favorite editor):

code .

If you get an error, you might need to install the code command in PATH.
TypeScript is the primary language for building SharePoint client-side web parts. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. SharePoint client-side
development tools are built using TypeScript classes, modules, and interfaces to help developers build robust client-side web parts.
The following are some key files in the project.

Web part class


HelloWorldWebPart.ts in the src\webparts\helloworld folder defines the main entry point for the web part. The web part class HelloWorldWebPart extends the
BaseClientSideWebPart. Any client-side web part should extend the BaseClientSideWebPart class to be defined as a valid web part.
BaseClientSideWebPart implements the minimal functionality that is required to build a web part. This class also provides many parameters to validate and access read-only properties
such as displayMode, web part properties, web part context, web part instanceId, the web part domElement, and much more.
Notice that the web part class is defined to accept a property type IHelloWorldWebPartProps.
The property type is defined as an interface before the HelloWorldWebPart class in the HelloWorldWebPart.ts file.

export interface IHelloWorldWebPartProps {


description: string;
}

This property definition is used to define custom property types for your web part, which is described in the property pane section later.
Web part render method
The DOM element where the web part should be rendered is available in the render method. This method is used to render the web part inside that DOM element. In the HelloWorld web
part, the DOM element is set to a DIV. The method parameters include the display mode (either Read or Edit) and the configured web part properties if any:

public render(): void {


this.domElement.innerHTML = `
<div class="${ styles.helloWorld }">
<div class="${ styles.container }">
<div class="${ styles.row }">
<div class="${ styles.column }">
<span class="${ styles.title }">Welcome to SharePoint!</span>
<p class="${ styles.subTitle }">Customize SharePoint experiences using web parts.</p>
<p class="${ styles.description }">${escape(this.properties.description)}</p>
<a href="https://aka.ms/spfx" class="${ styles.button }">
<span class="${ styles.label }">Learn more</span>
</a>
</div>
</div>
</div>
</div>`;
}

This model is flexible enough so that web parts can be built in any JavaScript framework and loaded into the DOM element.
Configure the Web part property pane
The property pane is defined in the HelloWorldWebPart class. The propertyPaneSettings property is where you need to define the property pane.
When the properties are defined, you can access them in your web part by using this.properties.<property-value> , as shown in the render method:

<p class="${styles.description}">${escape(this.properties.description)}</p>

Notice that we are performing an HTML escape on the property's value to ensure a valid string. To learn more about how to work with the property pane and property pane field types, see
Make your SharePoint client-side web part configurable.
Let's now add a few more properties to the property pane: a check box, a drop-down list, and a toggle. We first start by importing the respective property pane fields from the framework.
1. Scroll to the top of the file and add the following to the import section from @microsoft/sp-webpart-base :

PropertyPaneCheckbox,
PropertyPaneDropdown,
PropertyPaneToggle

The complete import section looks like the following:

import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneCheckbox,
PropertyPaneDropdown,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';

2. Update the web part properties to include the new properties. This maps the fields to typed objects.
3. Replace the IHelloWorldWebPartProps interface with the following code.

export interface IHelloWorldWebPartProps {


description: string;
test: string;
test1: boolean;
test2: string;
test3: boolean;
}

4. Save the file.


5. Replace the getPropertyPaneConfiguration method with the following code, which adds the new property pane fields and maps them to their respective typed objects.
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: 'Description'
}),
PropertyPaneTextField('test', {
label: 'Multi-line Text Field',
multiline: true
}),
PropertyPaneCheckbox('test1', {
text: 'Checkbox'
}),
PropertyPaneDropdown('test2', {
label: 'Dropdown',
options: [
{ key: '1', text: 'One' },
{ key: '2', text: 'Two' },
{ key: '3', text: 'Three' },
{ key: '4', text: 'Four' }
]}),
PropertyPaneToggle('test3', {
label: 'Toggle',
onText: 'On',
offText: 'Off'
})
]
}
]
}
]
};
}

6. After you add your properties to the web part properties, you can now access the properties in the same way you accessed the description property earlier:

<p class="${ styles.description }">${escape(this.properties.test)}</p>

To set the default value for the properties, you need to update the web part manifest's properties property bag.
7. Open HelloWorldWebPart.manifest.json and modify the properties to:

"properties": {
"description": "HelloWorld",
"test": "Multi-line text field",
"test1": true,
"test2": "2",
"test3": true
}

The web part property pane now has these default values for those properties.

Web part manifest


The HelloWorldWebPart.manifest.json file defines the web part metadata such as version, id, display name, icon, and description. Every web part must contain this manifest.

{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "7d5437ee-afc2-4e66-914b-80be5ace4056",
"alias": "HelloWorldWebPart",
"componentType": "WebPart",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,

"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "HelloWorld" },
"description": { "default": "HelloWorld description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "HelloWorld",
"test": "Multi-line text field",
"test1": true,
"test2": "2",
"test3": true
}
}]
}

Now that we have introduced new properties, ensure that you are again hosting the web part from the local development environment by executing the following command. This also
ensures that the previous changes were correctly applied.

gulp serve

Preview the web part in SharePoint


SharePoint Workbench is also hosted in SharePoint to preview and test your local web parts in development. The key advantage is that now you are running in SharePoint context and you
are able to interact with SharePoint data.
1. Go to the following URL: https://your-sharepoint-tenant.sharepoint.com/_layouts/workbench.aspx

NOTE

If you do not have the SPFx developer certificate installed, Workbench notifies you that it is configured not to load scripts from localhost. Stop the currently running process in the
console window, and execute the gulp trust-dev-cert command in your project directory console to install the developer certificate before running the gulp serve command again.

2. Notice that the SharePoint Workbench now has the Office 365 Suite navigation bar.
3. Select the add icon in the canvas to reveal the toolbox. The toolbox now shows the web parts available on the site where the SharePoint Workbench is hosted along with your
HelloWorldWebPart.

4. Add HelloWorld from the toolbox. Now you're running your web part in a page hosted in SharePoint!

NOTE

The color of the web part depends on the colors of the site. By default, web parts inherit the core colors from the site by dynamically referencing Office UI Fabric Core styles used in the site
where the web part is hosted.
Because you are still developing and testing your web part, there is no need to package and deploy your web part to SharePoint.
Next steps
Congratulations on getting your first Hello World web part running!
Now that your web part is running, you can continue building out your Hello World web part in the next topic, Connect your web part to SharePoint. You will use the same Hello World web
part project and add the ability to interact with SharePoint List REST APIs. Notice that the gulp serve command is still running in your console window (or in Visual Studio Code if you are
using that as editor). You can continue to let it run while you go to the next article.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, please report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your
input in advance.
Build your first SharePoint client-side web part (Hello World part 1)
6/18/2018 • 11 minutes to read Edit Online

Client-side web parts are client-side components that run inside the context of a SharePoint page. Client-side web parts can be deployed to SharePoint Online, and you can also use modern
JavaScript tools and libraries to build them.
Client-side web parts support:
Building with HTML and JavaScript.
Both SharePoint Online and on-premises environments.
NOTE

Before following the steps in this article, be sure to Set up your development environment.
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/YqUIX2pMUzg

Create a new web part project


To create a new web part project
1. Create a new project directory in your favorite location.

md helloworld-webpart

2. Go to the project directory.

cd helloworld-webpart

3. Create a new HelloWorld web part by running the Yeoman SharePoint Generator.

yo @microsoft/sharepoint

4. When prompted:
Accept the default helloworld-webpart as your solution name, and then select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder for where to place the files.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select WebPart as the client-side component type to be created.
5. The next set of prompts ask for specific information about your web part:
Accept the default HelloWorld as your web part name, and then select Enter.
Accept the default HelloWorld description as your web part description, and then select Enter.
Accept the default No javascript web framework as the framework you would like to use, and then select Enter.
At this point, Yeoman installs the required dependencies and scaffolds the solution files along with the HelloWorld web part. This might take a few minutes.
When the scaffold is complete, you should see the following message indicating a successful scaffold.

For information about troubleshooting any errors, see Known issues.

Using your favorite Code Editor


Because the SharePoint client-side solution is HTML/TypeScript based, you can use any code editor that supports client-side development to build your web part, such as:
Visual Studio Code
Atom
Webstorm
SharePoint Framework documentation uses Visual Studio code in the steps and examples. Visual Studio Code is a lightweight but powerful source code editor from Microsoft that runs on
your desktop and is available for Windows, Mac, and Linux. It comes with built-in support for JavaScript, TypeScript, and Node.js, and has a rich ecosystem of extensions for other languages
(such as C++, C#, Python, PHP) and runtimes.

Preview the web part


To preview your web part, build and run it on a local web server. The client-side toolchain uses HTTPS endpoint by default. However, because a default certificate is not configured for the
local dev environment, your browser reports a certificate error. The SPFx toolchain comes with a developer certificate that you can install for building web parts.

To install the developer certificate and preview your web part


1. Switch to your console, ensure that you are still in the helloworld-webpart directory, and then enter the following command:

gulp trust-dev-cert

2. Now that we have installed the developer certificate, enter the following command in the console to build and preview your web part:

gulp serve

This command executes a series of gulp tasks to create a local, node-based HTTPS server on localhost:4321 and launches your default browser to preview web parts from your local dev
environment.
SharePoint client-side development tools use gulp as the task runner to handle build process tasks such as:
Bundling and minifying JavaScript and CSS files.
Running tools to call the bundling and minification tasks before each build.
Compiling SASS files to CSS.
Compiling TypeScript files to JavaScript.
Visual Studio Code provides built-in support for gulp and other task runners. Select Ctrl+Shift+B on Windows or Cmd+Shift+B on Mac to debug and preview your web part.
SharePoint Workbench is a developer design surface that enables you to quickly preview and test web parts without deploying them in SharePoint. SharePoint Workbench includes the
client-side page and the client-side canvas in which you can add, delete, and test your web parts in development.

To use SharePoint Workbench to preview and test your web part


1. To add the HelloWorld web part, select the add icon (this icon appears when you mouse hovers over a section as shown in the previous image). This opens the toolbox where you can
see a list of web parts available for you to add. The list includes the HelloWorld web part as well other web parts available locally in your development environment.

2. Select HelloWorld to add the web part to the page.


Congratulations! You have just added your first client-side web part to a client-side page.
3. Select the pencil icon on the far left of the web part to reveal the web part property pane.

The property pane is where you can define properties to customize your web part. The property pane is client-side driven and provides a consistent design across SharePoint.
4. Modify the text in the Description text box to Client-side web parts are awesome!
Notice how the text in the web part also changes as you type.
One of the new capabilities available to the property pane is to configure its update behavior, which can be set to reactive or non-reactive. By default, the update behavior is reactive and
enables you to see the changes as you edit the properties. The changes are saved instantly when the behavior is reactive.

Web part project structure


To use Visual Studio Code to explore the web part project structure
1. In the console, break the processing by selecting Ctrl+C (in Windows).
2. Enter the following command to open the web part project in Visual Studio Code (or use your favorite editor):

code .

If you get an error, you might need to install the code command in PATH.
TypeScript is the primary language for building SharePoint client-side web parts. TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. SharePoint client-side
development tools are built using TypeScript classes, modules, and interfaces to help developers build robust client-side web parts.
The following are some key files in the project.

Web part class


HelloWorldWebPart.ts in the src\webparts\helloworld folder defines the main entry point for the web part. The web part class HelloWorldWebPart extends the
BaseClientSideWebPart. Any client-side web part should extend the BaseClientSideWebPart class to be defined as a valid web part.
BaseClientSideWebPart implements the minimal functionality that is required to build a web part. This class also provides many parameters to validate and access read-only properties
such as displayMode, web part properties, web part context, web part instanceId, the web part domElement, and much more.
Notice that the web part class is defined to accept a property type IHelloWorldWebPartProps.
The property type is defined as an interface before the HelloWorldWebPart class in the HelloWorldWebPart.ts file.

export interface IHelloWorldWebPartProps {


description: string;
}

This property definition is used to define custom property types for your web part, which is described in the property pane section later.
Web part render method
The DOM element where the web part should be rendered is available in the render method. This method is used to render the web part inside that DOM element. In the HelloWorld web
part, the DOM element is set to a DIV. The method parameters include the display mode (either Read or Edit) and the configured web part properties if any:

public render(): void {


this.domElement.innerHTML = `
<div class="${ styles.helloWorld }">
<div class="${ styles.container }">
<div class="${ styles.row }">
<div class="${ styles.column }">
<span class="${ styles.title }">Welcome to SharePoint!</span>
<p class="${ styles.subTitle }">Customize SharePoint experiences using web parts.</p>
<p class="${ styles.description }">${escape(this.properties.description)}</p>
<a href="https://aka.ms/spfx" class="${ styles.button }">
<span class="${ styles.label }">Learn more</span>
</a>
</div>
</div>
</div>
</div>`;
}

This model is flexible enough so that web parts can be built in any JavaScript framework and loaded into the DOM element.
Configure the Web part property pane
The property pane is defined in the HelloWorldWebPart class. The propertyPaneSettings property is where you need to define the property pane.
When the properties are defined, you can access them in your web part by using this.properties.<property-value> , as shown in the render method:

<p class="${styles.description}">${escape(this.properties.description)}</p>

Notice that we are performing an HTML escape on the property's value to ensure a valid string. To learn more about how to work with the property pane and property pane field types, see
Make your SharePoint client-side web part configurable.
Let's now add a few more properties to the property pane: a check box, a drop-down list, and a toggle. We first start by importing the respective property pane fields from the framework.
1. Scroll to the top of the file and add the following to the import section from @microsoft/sp-webpart-base :

PropertyPaneCheckbox,
PropertyPaneDropdown,
PropertyPaneToggle

The complete import section looks like the following:

import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneCheckbox,
PropertyPaneDropdown,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';

2. Update the web part properties to include the new properties. This maps the fields to typed objects.
3. Replace the IHelloWorldWebPartProps interface with the following code.

export interface IHelloWorldWebPartProps {


description: string;
test: string;
test1: boolean;
test2: string;
test3: boolean;
}

4. Save the file.


5. Replace the getPropertyPaneConfiguration method with the following code, which adds the new property pane fields and maps them to their respective typed objects.
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: 'Description'
}),
PropertyPaneTextField('test', {
label: 'Multi-line Text Field',
multiline: true
}),
PropertyPaneCheckbox('test1', {
text: 'Checkbox'
}),
PropertyPaneDropdown('test2', {
label: 'Dropdown',
options: [
{ key: '1', text: 'One' },
{ key: '2', text: 'Two' },
{ key: '3', text: 'Three' },
{ key: '4', text: 'Four' }
]}),
PropertyPaneToggle('test3', {
label: 'Toggle',
onText: 'On',
offText: 'Off'
})
]
}
]
}
]
};
}

6. After you add your properties to the web part properties, you can now access the properties in the same way you accessed the description property earlier:

<p class="${ styles.description }">${escape(this.properties.test)}</p>

To set the default value for the properties, you need to update the web part manifest's properties property bag.
7. Open HelloWorldWebPart.manifest.json and modify the properties to:

"properties": {
"description": "HelloWorld",
"test": "Multi-line text field",
"test1": true,
"test2": "2",
"test3": true
}

The web part property pane now has these default values for those properties.

Web part manifest


The HelloWorldWebPart.manifest.json file defines the web part metadata such as version, id, display name, icon, and description. Every web part must contain this manifest.

{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "7d5437ee-afc2-4e66-914b-80be5ace4056",
"alias": "HelloWorldWebPart",
"componentType": "WebPart",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,

"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "HelloWorld" },
"description": { "default": "HelloWorld description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "HelloWorld",
"test": "Multi-line text field",
"test1": true,
"test2": "2",
"test3": true
}
}]
}

Now that we have introduced new properties, ensure that you are again hosting the web part from the local development environment by executing the following command. This also
ensures that the previous changes were correctly applied.

gulp serve

Preview the web part in SharePoint


SharePoint Workbench is also hosted in SharePoint to preview and test your local web parts in development. The key advantage is that now you are running in SharePoint context and you
are able to interact with SharePoint data.
1. Go to the following URL: https://your-sharepoint-tenant.sharepoint.com/_layouts/workbench.aspx

NOTE

If you do not have the SPFx developer certificate installed, Workbench notifies you that it is configured not to load scripts from localhost. Stop the currently running process in the
console window, and execute the gulp trust-dev-cert command in your project directory console to install the developer certificate before running the gulp serve command again.

2. Notice that the SharePoint Workbench now has the Office 365 Suite navigation bar.
3. Select the add icon in the canvas to reveal the toolbox. The toolbox now shows the web parts available on the site where the SharePoint Workbench is hosted along with your
HelloWorldWebPart.

4. Add HelloWorld from the toolbox. Now you're running your web part in a page hosted in SharePoint!

NOTE

The color of the web part depends on the colors of the site. By default, web parts inherit the core colors from the site by dynamically referencing Office UI Fabric Core styles used in the site
where the web part is hosted.
Because you are still developing and testing your web part, there is no need to package and deploy your web part to SharePoint.
Next steps
Congratulations on getting your first Hello World web part running!
Now that your web part is running, you can continue building out your Hello World web part in the next topic, Connect your web part to SharePoint. You will use the same Hello World web
part project and add the ability to interact with SharePoint List REST APIs. Notice that the gulp serve command is still running in your console window (or in Visual Studio Code if you are
using that as editor). You can continue to let it run while you go to the next article.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, please report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your
input in advance.
Connect your client-side web part to SharePoint (Hello World part 2)
5/3/2018 • 9 minutes to read Edit Online

Connect your web part to SharePoint to access functionality and data in SharePoint and provide a more integrated experience for end users. This article continues building the Hello World
web part built in the previous article Build your first web part.
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/hYrP6D4FaaU

Run gulp serve


Ensure that you have the gulp serve command running. If it is not already running, go to the helloworld-webpart project directory and run it by using the following commands.

cd helloworld-webpart
gulp serve

Get access to page context


When the Workbench is hosted locally, you do not have the SharePoint page context. You can still test your web part in many different ways. For example, you can concentrate on building
the web part's UX and use mock data to simulate SharePoint interaction when you don't have the SharePoint context.
However, when the Workbench is hosted in SharePoint, you get access to the page context, which provides various key properties such as:
Web title
Web absolute URL
Web server-relative URL
User sign-in name

To get access to the page context


1. Use the following variable in your web part class:

this.context.pageContext

2. Switch to Visual Studio code (or your preferred IDE) and open src\webparts\helloWorld\HelloWorldWebPart.ts.
3. Inside the render method, replace the innerHTML code block with the following code:
this.domElement.innerHTML = `
<div class="${ styles.helloWorld }">
<div class="${ styles.container }">
<div class="${ styles.row }">
<div class="${ styles.column }">
<span class="${ styles.title }">Welcome to SharePoint!</span>
<p class="${ styles.subTitle }">Customize SharePoint experiences using web parts.</p>
<p class="${ styles.description }">${escape(this.properties.description)}</p>
<p class="${ styles.description }">${escape(this.properties.test)}</p>
<p class="${ styles.description }">Loading from ${escape(this.context.pageContext.web.title)}</p>
<a href="https://aka.ms/spfx" class="${ styles.button }">
<span class="${ styles.label }">Learn more</span>
</a>
</div>
</div>
</div>
</div>`;

4. Notice how ${ } is used to output the variable's value in the HTML block. An extra HTML p is used to display this.context.pageContext.web.title . Because this web part loads
from the local environment, the title is Local Workbench.
5. Save the file. The gulp serve running in your console detects this save operation and:
Builds and bundles the updated code automatically.
Refreshes your local Workbench page (as the web part code needs to be reloaded).
NOTE

Keep the console window and Visual Studio Code side-by-side to see gulp automatically compile as you save changes in Visual Studio Code.
6. In your browser, switch to the local SharePoint Workbench tab. If you have already closed the tab, the URL is https://localhost:4321/temp/workbench.html .
You should see the following in the web part:

7. Navigate to the SharePoint Workbench hosted in SharePoint. The full URL is https://your-sharepoint-site-url/_layouts/workbench.aspx . Notice that on the SharePoint Online side,
you need to refresh the page to see the changes.
NOTE

If you do not have the SPFx developer certificate installed, Workbench notifies you that it is configured not to load scripts from localhost. Execute gulp trust-dev-cert command in
your project directory console to install the developer certificate.
You should now see your SharePoint site title in the web part now that page context is available to the web part.
Define list model
You need a list model to start working with SharePoint list data. To retrieve the lists, you need two models.
1. Switch to Visual Studio Code and go to src\webparts\helloWorld\HelloWorldWebPart.ts.
2. Define the following interface models just above the HelloWorldWebPart class:

export interface ISPLists {


value: ISPList[];
}

export interface ISPList {


Title: string;
Id: string;
}

The ISPList interface holds the SharePoint list information that we are connecting to.

Retrieve lists from mock store


To test in the local Workbench, you need a mock store that returns mock data.

To create a mock store


1. Create a new file inside the src\webparts\helloWorld folder named MockHttpClient.ts.
2. Copy the following code into MockHttpClient.ts:

import { ISPList } from './HelloWorldWebPart';

export default class MockHttpClient {

private static _items: ISPList[] = [{ Title: 'Mock List', Id: '1' },


{ Title: 'Mock List 2', Id: '2' },
{ Title: 'Mock List 3', Id: '3' }];

public static get(): Promise<ISPList[]> {


return new Promise<ISPList[]>((resolve) => {
resolve(MockHttpClient._items);
});
}
}

Things to note about the code:


Because there are multiple exports in HelloWorldWebPart.ts, the specific one to import is specified by using { } . In this case, only the data model ISPList is required.
You do not need to type the file extension when importing from the default module, which in this case is HelloWorldWebPart.
It exports the MockHttpClient class as a default module so that it can be imported in other files.
It builds the initial ISPList mock array and returns.
3. Save the file.
You can now use the MockHttpClient class in the HelloWorldWebPart class. You first need to import the MockHttpClient module.

To import the MockHttpClient module


1. Open the HelloWorldWebPart.ts file.
2. Copy and paste the following code just under import * as strings from 'HelloWorldWebPartStrings'; .

import MockHttpClient from './MockHttpClient';

3. Add the following private method that mocks the list retrieval inside the HelloWorldWebPart class.
private _getMockListData(): Promise<ISPLists> {
return MockHttpClient.get()
.then((data: ISPList[]) => {
var listData: ISPLists = { value: data };
return listData;
}) as Promise<ISPLists>;
}

4. Save the file.

Retrieve lists from SharePoint site


Next you need to retrieve lists from the current site. You will use SharePoint REST APIs to retrieve the lists from the site, which are located at
https://yourtenantprefix.sharepoint.com/_api/web/lists .

SharePoint Framework includes a helper class spHttpClient to execute REST API requests against SharePoint. It adds default headers, manages the digest needed for writes, and collects
telemetry that helps the service to monitor the performance of an application.

To use this helper class, import them from the @microsoft/sp-http module
1. Scroll to the top of the HelloWorldWebPart.ts file.
2. Copy and paste the following code just under import MockHttpClient from './MockHttpClient'; :

import {
SPHttpClient,
SPHttpClientResponse
} from '@microsoft/sp-http';

3. Add the following private method to retrieve lists from SharePoint inside the HelloWorldWebPart class.

private _getListData(): Promise<ISPLists> {


return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + `/_api/web/lists?$filter=Hidden eq false`, SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}

The method uses the spHttpClient helper class and issues a get request. It uses the ISPLists model and also applies a filter to not retrieve hidden lists.
4. Save the file.
5. Switch to the console window that is running gulp serve and check if there are any errors. If there are errors, gulp reports them in the console, and you need to fix them before
proceeding.

Add new styles


The SharePoint Framework uses Sass as the CSS pre-processor, and specifically uses the SCSS syntax, which is fully compliant with normal CSS syntax. Sass extends the CSS language and
allows you to use features such as variables, nested rules, and inline imports to organize and create efficient style sheets for your web parts. The SharePoint Framework already comes with a
SCSS compiler that converts your Sass files to normal CSS files, and also provides a typed version to use during development.

To add new styles


1. Open HelloWorld.module.scss. This is the SCSS file where you define your styles.
By default, the styles are scoped to your web part. You can see that as the styles are defined under .helloWorld.
2. Add the following styles after the .button style, but still inside the main .helloWorld style section:

.list {
color: #333333;
font-family: 'Segoe UI Regular WestEuropean', 'Segoe UI', Tahoma, Arial, sans-serif;
font-size: 14px;
font-weight: normal;
box-sizing: border-box;
margin: 10;
padding: 10;
line-height: 50px;
list-style-type: none;
box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

.listItem {
color: #333333;
vertical-align: center;
font-family: 'Segoe UI Regular WestEuropean', 'Segoe UI', Tahoma, Arial, sans-serif;
font-size: 14px;
font-weight: normal;
box-sizing: border-box;
margin: 0;
padding: 0;
box-shadow: none;
*zoom: 1;
padding: 9px 28px 3px;
position: relative;
}

3. Save the file.


Gulp rebuilds the code in the console as soon as you save the file. This generates the corresponding typings in the HelloWorld.module.scss.ts file. After compiled to TypeScript, you
can then import and reference these styles in your web part code.
You can see that in the render method of the web part:
<div class="${styles.row}">

Render lists information


Open the HelloWorldWebPart class.
SharePoint Workbench gives you the flexibility to test web parts in your local environment and from a SharePoint site. SharePoint Framework aids this capability by helping you understand
which environment your web part is running from by using the EnvironmentType module.

To use the EnvironmentType module


1. Import the Environment and the EnvironmentType modules from the @microsoft/sp-core-library bundle. Add it to the import section at the top as shown in the following code:

import {
Environment,
EnvironmentType
} from '@microsoft/sp-core-library';

2. Add the following private method inside the HelloWorldWebPart class to call the respective methods to retrieve list data:

private _renderListAsync(): void {


// Local environment
if (Environment.type === EnvironmentType.Local) {
this._getMockListData().then((response) => {
this._renderList(response.value);
});
}
else if (Environment.type == EnvironmentType.SharePoint ||
Environment.type == EnvironmentType.ClassicSharePoint) {
this._getListData()
.then((response) => {
this._renderList(response.value);
});
}
}

Things to note about hostType in the _renderListAsync method:


The Environment.type property helps you check if you are in a local or SharePoint environment.
The correct method is called depending on where your Workbench is hosted.
3. Save the file.
Now you need to render the list data with the value fetched from the REST API.
4. Add the following private method inside the HelloWorldWebPart class:

private _renderList(items: ISPList[]): void {


let html: string = '';
items.forEach((item: ISPList) => {
html += `
<ul class="${styles.list}">
<li class="${styles.listItem}">
<span class="ms-font-l">${item.Title}</span>
</li>
</ul>`;
});

const listContainer: Element = this.domElement.querySelector('#spListContainer');


listContainer.innerHTML = html;
}

The previous method references the new CSS styles added earlier by using the styles variable.
5. Save the file.

Retrieve list data


1. Navigate to the render method, and replace the code inside the method with the following code:

this.domElement.innerHTML = `
<div class="${ styles.helloWorld }">
<div class="${ styles.container }">
<div class="${ styles.row }">
<div class="${ styles.column }">
<span class="${ styles.title }">Welcome to SharePoint!</span>
<p class="${ styles.subTitle }">Customize SharePoint experiences using web parts.</p>
<p class="${ styles.description }">${escape(this.properties.description)}</p>
<p class="${ styles.description }">${escape(this.properties.test)}</p>
<p class="${ styles.description }">Loading from ${escape(this.context.pageContext.web.title)}</p>
<a href="https://aka.ms/spfx" class="${ styles.button }">
<span class="${ styles.label }">Learn more</span>
</a>
</div>
</div>
<div id="spListContainer" />
</div>
</div>`;

this._renderListAsync();

2. Save the file.


Notice in the gulp serve console window that it rebuilds the code. Make sure you don't see any errors.
3. Switch to your local Workbench and add the HelloWorld web part.
You should see the mock data returned.

4. Switch to the Workbench hosted in SharePoint. Refresh the page and add the HelloWorld web part.
You should see lists returned from the current site.

5. Now you can stop the server from running. Switch to the console and stop gulp serve . Select Ctrl+C to terminate the gulp task.

Next steps
Congratulations on connecting your web part to SharePoint list data!
You can continue building out your Hello World web part in the next topic Deploy your web part to a SharePoint page. You will learn how to deploy and preview the Hello World web part in
a SharePoint page.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Deploy your client-side web part to a SharePoint page (Hello World part 3)
5/3/2018 • 4 minutes to read Edit Online

Ensure that you have completed the procedures in the following articles before you start:
Build your first SharePoint client-side web part
Connect your client-side web part to SharePoint
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/BpJ01ahxbiY

Package the HelloWorld web part


1. In the console window, go to the web part project directory created in Build your first SharePoint client-side web part.

cd helloworld-webpart

2. If gulp serve is still running, stop it from running by selecting Ctrl+C.


Unlike in the Workbench, to use client-side web parts on modern SharePoint server-side pages, you need to deploy and register the web part with SharePoint. First you need to
package the web part.
3. Open the HelloWorldWebPart web part project in Visual Studio Code, or your preferred IDE.
4. Open package-solution.json from the config folder.
The package-solution.json file defines the package metadata as shown in the following code:

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "helloworld-webpart-client-side-solution",
"id": "4432f33b-5845-4ca0-827e-a8ae68c7b945",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/helloworld-webpart.sppkg"
}
}

5. In the console window, enter the following command to package your client-side solution that contains the web part:

gulp package-solution

The command creates the package in the sharepoint/solution folder:

helloworld-webpart.sppkg
Package contents
The package uses SharePoint Feature to package your web part. By default, the gulp task creates a feature for your web part.
You can view the raw package contents in the sharepoint/debug folder.
The contents are then packaged into an .sppkg file. The package format is very similar to a SharePoint Add-ins package and uses Microsoft Open Packaging Conventions to package your
solution.
The JavaScript files, CSS, and other assets are packaged inside of the package when the --ship option is used. In this case, however, we will first test deployment and capabilities by hosting
JavaScript files from localhost. This deployment option is explained in the next tutorial.
NOTE

Starting from the SharePoint Framework v1.4, static assets are by default packaged inside of the sppkg package. When a package is deployed in the app catalog, the assets are automatically
hosted either from Office 365 CDN (if enabled) or from an app catalog URL. You can control this behavior with the includeClientSideAssets setting in the package-solution.json file.

Deploy the HelloWorld package to app catalog


Next, you need to deploy the package that was generated to the app catalog.
NOTE

If you do not have an app catalog, a SharePoint Online Admin can create one by following the instructions in this guide: Use the App Catalog to make custom business apps available for
your SharePoint Online environment.
1. Go to your site's app catalog.
2. Upload or drag and drop the helloworld-webpart.sppkg to the app catalog.

This deploys the client-side solution package. Because this is a full trust client-side solution, SharePoint displays a dialog and asks you to trust the client-side solution to deploy.

3. Select Deploy.

Install the client-side solution on your site


1. Go to your developer site collection.
2. Select the gears icon on the top nav bar on the right, and then select Add an app to go to your Apps page.
3. In the Search box, enter helloworld, and select Enter to filter your apps.
4. Select the helloworld-webpart-client-side-solution app to install the app on the site.

The client-side solution and the web part are installed on your developer site.
The Site Contents page shows you the installation status of your client-side solution. Make sure the installation is complete before going to the next step.

Preview the web part on a SharePoint page


Now that you have deployed and installed the client-side solution, add the web part to a SharePoint page. Remember that resources such as JavaScript and CSS are available from the local
computer.
1. Open the <your-webpart-guid>.manifest.json from the \dist folder.
Notice that the internalModuleBaseUrls property in the loaderConfig entry still refers to your local computer:

"internalModuleBaseUrls": [
"https://`your-local-machine-name`:4321/"
]

2. Before adding the web part to a SharePoint server-side page, run the local server.
3. In the console window that has the helloworld-webpart project directory, run the gulp task to start serving from localhost:

gulp serve --nobrowser

NOTE

--nobrowser will not automatically launch the SharePoint Workbench.

Add the HelloWorld web part to modern page


1. In your browser, go to your site where the solution was just installed.
2. Select the gears icon in the top nav bar on the right, and then select Add a page.
3. Edit the page.
4. Open the web part picker and select your HelloWorld web part.
The web part assets are loaded from the local environment. To load the scripts hosted on your local computer, you need to enable the browser to load unsafe scripts. Depending on the
browser you are using, make sure you enable loading unsafe scripts for this session.
You should see the HelloWorld web part you built in the previous article that retrieves lists from the current site.

Edit web part properties


1. Select the Configure element icon (pen) in the web part to open the property pane for the web part.

This is the same property pane you built and previewed in the Workbench.
2. Edit the Description property, and enter Client-side web parts are awesome!
3. Notice that you still have the same behaviors such as a reactive pane where the web part is updated as you type.
4. Select the x icon to close the client-side property pane.
5. On the toolbar, select Save and close to save the page.

Next steps
Congratulations! You have deployed a client-side web part to a modern SharePoint page.
You can continue building out your Hello World web part in the next topic Hosting client-side web part from Office 365 CDN, where you will learn how to deploy and load the web part
assets from an Office 365 CDN instead of localhost.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Host your client-side web part from Office 365 CDN (Hello World part 4)
4/20/2018 • 6 minutes to read Edit Online

Office 365 Content Delivery Network (CDN) provides you an easy solution to host your assets directly from your own Office 365 tenant. It can be used for hosting any static assets that are
used in SharePoint Online.
NOTE

There are multiple different hosting options for your web part assets. This tutorial concentrates on showing the Office 365 CDN option, but you could also use the Azure CDN or simply host
your assets from SharePoint library from your tenant. In the latter case, you would not benefit from the CDN performance improvements, but that would also work from the functionality
perspective. Any location that end users can access using HTTP(S) would be technically suitable for hosting the assets for end users.
IM P O R T A N T

This article uses the includeClientSideAssets attribute, which was introduced in the SPFx v1.4. This version is not supported with SharePoint 2016 Feature Pack 2. If you are using an on-
premises setup, you need to decide the CDN hosting location separately. You can also simply host the JavaScript files from a centralized library in your on-premises SharePoint to which
your users have access. Please see additional considerations in the SharePoint 2016 specific guidance.
Make sure that you have completed the following tasks before you begin:
Build your first client-side web part
Connect your client-side web part to SharePoint
Deploy your client-side web part to a SharePoint page
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/MEZMs8VMVQ0

Enable CDN in your Office 365 tenant


1. Ensure that you have the latest version of the SharePoint Online Management Shell by downloading it from the Microsoft Download site.
 T IP

If you are using a non-Windows machine, you cannot use the SharePoint Online Management Shell. You can, however, manage these settings by using Office 365 CLI.
2. Connect to your SharePoint Online tenant with a PowerShell session.

Connect-SPOService -Url https://contoso-admin.sharepoint.com

3. Get the current status of public CDN settings from the tenant level by executing the following commands one-by-one.

Get-SPOTenantCdnEnabled -CdnType Public


Get-SPOTenantCdnOrigins -CdnType Public
Get-SPOTenantCdnPolicies -CdnType Public

SharePoint Framework solutions can automatically benefit from the Office 365 Public CDN as long as it's enabled in your tenant. When CDN is enabled, */CLIENTSIDEASSETS origin is
automatically added as a valid origin.
1. Enable public CDN in the tenant.

Set-SPOTenantCdnEnabled -CdnType Public


2. Confirm settings by selecting Y and then Enter.

Now public CDN has been enabled in the tenant by using the default file type configuration allowed. This means that the following file type extensions are supported: CSS, EOT, GIF,
ICO, JPEG, JPG, JS, MAP, PNG, SVG, TTF, and WOFF.
SharePoint Framework solutions can automatically benefit from the Office 365 Public CDN as long as it's enabled in your tenant. When CDN is enabled, the */CLIENTSIDEASSETS
origin is automatically added as a valid origin.
NOTE

If you have previously enabled Office 365 CDN, you should re-enable the public CDN so that you have the */CLIENTSIDEASSETS entry added as a valid CDN origin for public CDN. If
this entry is not present and the public CDN is enabled in your tenant, bundle requests will contain the token hostname spclientsideassetlibrary in their URL, causing the requests to
fail.
3. You can double-check the current setup of your end-points. Execute the following command to get the list of CDN origins from your tenant:

Get-SPOTenantCdnOrigins -CdnType Public

Notice that your newly added origin is listed as a valid CDN origin. Final configuration of the origin takes a while (approximately 15 minutes), so we can continue by creating your test
web part, which will be hosted from the origin when the deployment is completed.

NOTE

When the origin is listed without the (configuration pending) text, it is ready to be used in your tenant. This is the indication of an on-going configuration between SharePoint Online
and the CDN system.

End task in project directory


1. Switch to the console and make sure you are still in the project directory that you used to set up your web part project.
2. End the possible gulp serve task by selecting Ctrl+C, and ensure that you are in your project directory:

cd helloworld-webpart

Review solution settings


1. Open the HelloWorldWebPart web part project in Visual Studio Code or your preferred IDE.
2. Open package-solution.json from the config folder.
The package-solution.json file defines the package metadata as shown in the following code:

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "helloworld-webpart-client-side-solution",
"id": "4432f33b-5845-4ca0-827e-a8ae68c7b945",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/helloworld-webpart.sppkg"
}
}

The default value for the includeClientSideAssets is true , which means that static assets are packaged automatically inside of the .sppkg files, and you do not need to separately host your
assets from an external system.
If Office 365 CDN is enabled, it is used automatically with default settings. If Office 365 CDN is not enabled, assets are served from the app catalog site collection.
NOTE

Starting from the SharePoint Framework v1.4, static assets are by default packaged inside of the sppkg package. When a package is deployed in the app catalog, they are automatically
hosted either from Office 365 CDN (if enabled) or from an app catalog URL. You can control this behavior with the includeClientSideAssets setting in the package-solution.json file.

Prepare web part assets to deploy


1. Execute the following task to bundle your solution. This executes a release build of your project by using a dynamic label as the host URL for your assets. This URL is automatically
updated based on your tenant CDN settings.

gulp bundle --ship

2. Execute the following task to package your solution. This creates an updated helloworld-webpart.sppkg package on the sharepoint/solution folder.

gulp package-solution --ship

NOTE

If you are interested in what actually got packaged inside of the sppkg file, you can look in the content of the sharepoint/solution/debug folder.
3. Upload or drag and drop the newly created client-side solution package to the app catalog in your tenant.
4. Because you already deployed the package, you are prompted as to whether to replace the existing package. Select Replace It.

5. Notice how the domain list in the prompt says SharePoint Online. This is because the content is either served from the Office 365 CDN or from the app catalog, depending on the
tenant settings. Select Deploy.

6. Open the site where you previously installed the helloworld-webpart-client-side-solution or install the solution to a new site.
7. After the solution has been installed, select Add a page from the gear menu, and select HelloWorld from the modern page web part picker to add your custom web part to page.

8. Notice how the web part is rendered even though you are not running the node.js service locally.
9. Save changes on the page with the web part.
10. Select F12 to open up developer tools.
11. Extend publiccdn.sharepointonline.com under the source and notice how the hello-world-web-part file is loaded from the Public CDN URL pointing dynamically to a library
located under the app catalog site collection.

NOTE

If you would not have CDN enabled in your tenant, and the includeClientSideAssets setting would be true in the package-solution.json, the loading URL for the assets would be
dynamically updated and pointing directly to the ClientSideAssets folder located in the app catalog site collection. In this example case, the URL would be
https://sppnp.microsoft.com/sites/apps/ClientSideAssets/ .

Now you have deployed your custom web part to SharePoint Online and it's being hosted automatically from the Office 365 CDN.

Next steps
You can load jQuery and jQuery UI and build a jQuery Accordion web part. To continue, see Add jQueryUI Accordion to your client-side web part.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Add jQueryUI Accordion to your SharePoint client-side web part
4/20/2018 • 7 minutes to read Edit Online

Adding the jQueryUI Accordion to your web part project involves creating a new web part, as shown in the following image.

Ensure that you've completed the following steps before you start:
Build your first web part
Connect your web part to SharePoint
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/7UOxTbMMPrQ

The developer toolchain uses Webpack, SystemJS, and CommonJS to bundle your web parts. This includes loading any external dependencies such as jQuery or jQueryUI. To load external
dependencies, at a high level, you need to:
Acquire the external library, either via npm or download from the vendor.
If available, install the respective framework's TypeScript type definitions.
If required, update your solution config to not include the external dependency in your web part bundle by default.

Create a new web part project


1. Create a new project directory in your favorite location:
md jquery-webpart

W A R N IN G

Make sure to create this directory in a new folder, not as a subdirectory of helloworld-webpart .
2. Go to the project directory:

cd jquery-webpart

3. Create a new jQuery web part by running the Yeoman SharePoint Generator:

yo @microsoft/sharepoint

4. When prompted:
Accept the default jquery-webpart as your solution name. and select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder for where to place the files.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select WebPart as the client-side component type to be created.
5. The next set of prompts ask for specific information about your web part:
Enter jQuery for the web part name, and select Enter.
Enter jQuery Web Part as the description of the web part, and select Enter.
Accept the default No JavaScript framework option for the framework, and select Enter to continue.
At this point, Yeoman installs the required dependencies and scaffolds the solution files. This might take a few minutes. Yeoman scaffolds the project to include your jQueryWebPart
as well.
6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Enter the following to open the web part project in Visual Studio Code:

code .

Install jQuery and jQuery UI NPM Packages


1. In the console, enter the following to install the jQuery npm package:

npm install --save jquery@2

2. Now enter the following to install the jQueryUI npm package:

npm install --save jqueryui

Next, we need to install the typings for our project. Starting from TypeScript 2.0, we can use npm to install needed typings.
3. Open your console and install the needed types:

npm install --save @types/jquery@2


npm install --save @types/jqueryui

To unbundle external dependencies from web part bundle


By default, any dependencies you add are bundled into the web part bundle. In some cases, this is not ideal. You can choose to unbundle these dependencies from the web part bundle.
1. In Visual Studio Code, open the file config\config.json.
This file contains information about your bundle(s) and any external dependencies.
The bundles region contains the default bundle information; in this case, the jQuery web part bundle. When you add more web parts to your solution, you see one entry per web
part.

"bundles": {
"j-query-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/jQuery/JQueryWebPart.js",
"manifest": "./src/webparts/jQuery/JQueryWebPart.manifest.json"
}
]
}
},

2. The externals section contains the libraries that are not bundled with the default bundle.

"externals": {},

3. To exclude jQuery and jQueryUI from the default bundle, add the modules to the externals section:
"jquery":"node_modules/jquery/dist/jquery.min.js",
"jqueryui":"node_modules/jqueryui/jquery-ui.min.js"

Now when you build your project, jQuery and jQueryUI are not bundled into your default web part bundle.
The full content of the config.json file is currently as follows:

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"j-query-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/jQuery/JQueryWebPart.js",
"manifest": "./src/webparts/jQuery/JQueryWebPart.manifest.json"
}
]
}
},
"externals": {
"jquery":"node_modules/jquery/dist/jquery.min.js",
"jqueryui":"node_modules/jqueryui/jquery-ui.min.js"
},
"localizedResources": {
"JQueryWebPartStrings": "lib/webparts/jQuery/loc/{locale}.js"
}
}

Build the Accordion


Open the project folder jquery-webpart in Visual Studio Code. Your project should have the jQuery web part that you added earlier under the /src/webparts/jQuery folder.

To add the Accordion HTML


1. Add a new file in the src/webparts/jQuery folder called MyAccordionTemplate.ts.
2. Create and export (as a module) a class MyAccordionTemplate that holds the HTML code for the accordion.

export default class MyAccordionTemplate {


public static templateHtml: string = `
<div class="accordion">
<h3>Section 1</h3>
<div>
<p>
Mauris mauris ante, blandit et, ultrices a, suscipit eget, quam. Integer
ut neque. Vivamus nisi metus, molestie vel, gravida in, condimentum sit
amet, nunc. Nam a nibh. Donec suscipit eros. Nam mi. Proin viverra leo ut
odio. Curabitur malesuada. Vestibulum a velit eu ante scelerisque vulputate.
</p>
</div>
<h3>Section 2</h3>
<div>
<p>
Sed non urna. Donec et ante. Phasellus eu ligula. Vestibulum sit amet
purus. Vivamus hendrerit, dolor at aliquet laoreet, mauris turpis porttitor
velit, faucibus interdum tellus libero ac justo. Vivamus non quam. In
suscipit faucibus urna.
</p>
</div>
<h3>Section 3</h3>
<div>
<p>
Nam enim risus, molestie et, porta ac, aliquam ac, risus. Quisque lobortis.
Phasellus pellentesque purus in massa. Aenean in pede. Phasellus ac libero
ac tellus pellentesque semper. Sed ac felis. Sed commodo, magna quis
lacinia ornare, quam ante aliquam nisi, eu iaculis leo purus venenatis dui.
</p>
<ul>
<li>List item one</li>
<li>List item two</li>
<li>List item three</li>
</ul>
</div>
<h3>Section 4</h3>
<div>
<p>
Cras dictum. Pellentesque habitant morbi tristique senectus et netus
et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in
faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia
mauris vel est.
</p>
<p>
Suspendisse eu nisl. Nullam ut libero. Integer dignissim consequat lectus.
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per
inceptos himenaeos.
</p>
</div>
</div>`;
}

3. Save the file.

To import the Accordion HTML


1. In Visual Studio Code, open src\webparts\jQuery\JQueryWebPart.ts.
2. At the top of the file, where you can find other imports, add the following import:
import MyAccordionTemplate from './MyAccordionTemplate';

To import jQuery and jQueryUI


1. You can import jQuery to your web part in the same way that you imported MyAccordionTemplate. At the top of the file, where you can find other imports, add the following imports:

import * as jQuery from 'jquery';


import 'jqueryui';

2. Load some external CSS files by using the module loader. Add the following import:

import { SPComponentLoader } from '@microsoft/sp-loader';

3. Load the jQueryUI styles in the JQueryWebPart web part class by adding a constructor and using the newly imported SPComponentLoader. Add the following constructor to your web
part:

public constructor() {
super();

SPComponentLoader.loadCss('//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css');
}

This code does the following:


Calls the parent constructor with the context to initialize the web part.
Loads the accordion styles from a CDN asynchronously.

To render Accordion
1. In the jQueryWebPart.ts , go to the render method.
2. Set the web part's inner HTML to render the accordion HTML:

this.domElement.innerHTML = MyAccordionTemplate.templateHtml;

3. jQueryUI Accordion has a few options that you can set to customize the accordion. Define a few options for your accordion just under
this.domElement.innerHTML = MyAccordionTemplate.templateHtml; :

const accordionOptions: JQueryUI.AccordionOptions = {


animate: true,
collapsible: false,
icons: {
header: 'ui-icon-circle-arrow-e',
activeHeader: 'ui-icon-circle-arrow-s'
}
};

As you can see, the jQueryUI typed definition allows you to create a typed variable called JQueryUI.AccordionOptions and specify the supported properties.
If you play around with the IntelliSense, you notice that you get full support for available methods under JQueryUI. as well as the method parameters.
4. Finally, initialize the accordion:

jQuery('.accordion', this.domElement).accordion(accordionOptions);

As you can see, you use the variable jQuery that you used to import the jquery module. You then initialize the accordion.
The complete render method looks like this:

public render(): void {


this.domElement.innerHTML = MyAccordionTemplate.templateHtml;

const accordionOptions: JQueryUI.AccordionOptions = {


animate: true,
collapsible: false,
icons: {
header: 'ui-icon-circle-arrow-e',
activeHeader: 'ui-icon-circle-arrow-s'
}
};

jQuery('.accordion', this.domElement).accordion(accordionOptions);
}

5. Save the file.

Preview the web part


1. In your console, ensure that you are still in the jquery-webpart folder, and enter the following to build and preview your web part:

gulp serve

NOTE

Visual Studio Code provides built-in support for gulp and other task runners. You can select Ctrl+Shift+B in Windows or Cmd+Shift+B on a Mac to debug and preview your web
part.
Gulp executes the tasks and opens the local SharePoint web part Workbench.
2. In the page canvas, select the + (plus sign) to show the list of web parts, and add the jQuery web part. You should now see the jQueryUI Accordion!

3. In the console where you have gulp serve running, select Ctrl+C to terminate the task.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Use Office UI Fabric React components in your SharePoint client-side web part
5/3/2018 • 5 minutes to read Edit Online

This article describes how to build a simple web part that uses the DocumentCard component of Office UI Fabric React. Office UI Fabric React is the front-end framework for building
experiences for Office and Office 365. Fabric React includes a robust collection of responsive, mobile-first components that make it easy for you to create web experiences by using the Office
Design Language.
The following image shows a DocumentCard component created with Office UI Fabric React.

You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/1YRu4-nZot4

Create a new web part project


1. Create a new project directory in your favorite location:

md documentcardexample-webpart

2. Go to the project directory:

cd documentcardexample-webpart

3. Make sure you have the latest version of @microsoft/generator-sharepoint installed and create a new web part by running the Yeoman SharePoint generator:
yo @microsoft/sharepoint

4. When prompted:
Accept the default documentcardexample-webpart as your solution name, and select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder for where to place the files.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select WebPart as the client-side component type to be created.
5. The next set of prompts ask for specific information about your web part:
Use DocumentCardExample for your web part name, and select Enter.
Accept the default DocumentCardExample description, and select Enter.
Select React as the framework, and select Enter.
At this point, Yeoman installs the required dependencies and scaffolds the solution files. This might take a few minutes. Yeoman scaffolds the project to include your
DocumentCardExample web part as well.
6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Next, enter the following to open the web part project in Visual Studio Code:

code .

You now have a web part project with the React framework.
8. Open DocumentCardExampleWebPart.ts from the src\webparts\documentCardExample folder.
As you can see, the render method creates a react element and renders it in the web part DOM.

public render(): void {


const element: React.ReactElement<IDocumentCardExampleProps > = React.createElement(
DocumentCardExample,
{
description: this.properties.description
}
);

9. Open DocumentCardExample.tsx from the src\webparts\documentCardExample\components folder.


This is the main react component that Yeoman added to your project that renders in the web part DOM.

export default class DocumentCardExample extends React.Component<IDocumentCardExampleProps, {}> {


public render(): React.ReactElement<IDocumentCardExampleProps> {
return (
<div className={ styles.documentCardExample }>
<div className={ styles.container }>
<div className={ styles.row }>
<div className={ styles.column }>
<span className={ styles.title }>Welcome to SharePoint!</span>
<p className={ styles.subTitle }>Customize SharePoint experiences using web parts.</p>
<p className={ styles.description }>{escape(this.props.description)}</p>
<a href="https://aka.ms/spfx" className={ styles.button }>
<span className={ styles.label }>Learn more</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

Add an Office UI Fabric component


The new modern experiences in SharePoint use Office UI Fabric and Office UI Fabric React as the default front-end framework for building the new experiences. As a result, SharePoint
Framework ships with a default version of Office UI Fabric and Fabric React that matches the version available in SharePoint. This ensures that the web part you are building uses the right
version of the Fabric styles and components when deployed to SharePoint.
Because we chose React as our framework when creating the solution, the generator installed the right version of Office UI Fabric React as well. You can directly import the Fabric
components in your react components without any additional work.
NOTE

With the current release of the SharePoint Framework, we recommend that you use the Office UI Fabric and Fabric React that ships with the generator. We don't recommend that you update
the Office UI Fabric and Fabric React packages independently because that might conflict with the already available version in SharePoint, and as a result, your web part may fail to function
as expected.

To add an Office UI Fabric component


1. Open DocumentCardExample.tsx from the src\webparts\documentCardExample\components folder.
2. Add the following import statement to the top of the file to import Fabric React components that we want to use.
import {
DocumentCard,
DocumentCardPreview,
DocumentCardTitle,
DocumentCardActivity,
IDocumentCardPreviewProps
} from 'office-ui-fabric-react/lib/DocumentCard';

3. Delete the current render method, and add the following updated render method:

public render(): JSX.Element {


const previewProps: IDocumentCardPreviewProps = {
previewImages: [
{
previewImageSrc: String(require('./document-preview.png')),
iconSrc: String(require('./icon-ppt.png')),
width: 318,
height: 196,
accentColor: '#ce4b1f'
}
],
};

return (
<DocumentCard onClickHref='http://bing.com'>
<DocumentCardPreview { ...previewProps } />
<DocumentCardTitle title='Revenue stream proposal fiscal year 2016 version02.pptx' />
<DocumentCardActivity
activity='Created Feb 23, 2016'
people={
[
{ name: 'Kat Larrson', profileImageSrc: String(require('./avatar-kat.png')) }
]
}
/>
</DocumentCard>
);
}

4. Save the file.


In this code, the DocumentCard component includes some extra sections:
DocumentCardPreview
DocumentCardTitle
DocumentCardActivity
The previewProps property includes some properties of the DocumentCardPreview.
5. Notice the use of the relative path with a require statement to load images. Currently, you need to perform a small configuration in the gulpfile.js to enable these images to get
processed properly by webpack.
6. Open gulpfile.js from the root folder.
7. Add the following code just above the build.initialize(gulp); code line.

build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
if (build.getConfig().production) {
var basePath = build.writeManifests.taskConfig.cdnBasePath;
if (!basePath.endsWith('/')) {
basePath += '/';
}
generatedConfiguration.output.publicPath = basePath;
}
else {
generatedConfiguration.output.publicPath = "/dist/";
}
return generatedConfiguration;
}
});

8. Save the file.


Your full gulpfile.js file should look as follows.
'use strict';

const gulp = require('gulp');


const build = require('@microsoft/sp-build-web');
build.addSuppression(`Warning - [sass] The local CSS class 'ms-Grid' is not camelCase and will not be type-safe.`);

build.configureWebpack.mergeConfig({
additionalConfiguration: (generatedConfiguration) => {
if (build.getConfig().production) {
var basePath = build.writeManifests.taskConfig.cdnBasePath;
if (!basePath.endsWith('/')) {
basePath += '/';
}
generatedConfiguration.output.publicPath = basePath;
}
else {
generatedConfiguration.output.publicPath = "/dist/";
}
return generatedConfiguration;
}
});

build.initialize(gulp);

Copy the image assets


Copy the following images to your src\webparts\documentCardExample\components folder:
avatar-kat.png
icon-ppt.png
document-preview.png

Preview the web part in Workbench


1. In the console, enter the following to preview your web part in Workbench:

gulp serve

2. In the toolbox, select your DocumentCardExample web part to add:

NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.

See also
Build your first SharePoint client-side web part
Provision SharePoint assets from your SharePoint client-side web part
7/9/2018 • 12 minutes to read Edit Online

SharePoint assets can be provisioned as part of the SharePoint Framework solution, and are deployed to SharePoint sites when the solution is installed on it.
Before you start, complete the procedures in the following articles to ensure that you understand the basic flow of creating a custom client-side web part:
Build your first web part
Connect your web part to SharePoint
You can also follow these steps by watching this video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/qAqNk_X82QM

Create a new web part project


1. Create a new project directory in your favorite location:

md asset-deployment-webpart

2. Go to the project directory:

cd asset-deployment-webpart

3. Create a new client-side web part solution by running the Yeoman SharePoint Generator:

yo @microsoft/sharepoint

4. When prompted:
Accept the default asset-deployment-webpart as your solution name, and then select Enter.
Select SharePoint Online only (latest), and then select Enter.
Select Use the current folder as the location for the files.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select WebPart as the client-side component type to be created.
5. The next set of prompts ask for specific information about your web part:
Enter AssetDeployment for the web part name, and then select Enter.
Enter AssetDeployment Web Part as the description of the web part, and then select Enter.
Accept the default No JavaScipt web framework option for the framework, and then select Enter to continue.
At this point, Yeoman installs the required dependencies and scaffolds the solution files. This might take a few minutes. Yeoman scaffolds the project to include your
AssetDeployment web part as well.
6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Next, enter the following to open the web part project in Visual Studio Code:

code .

Create folder structure for your SharePoint assets


We first need to create an assets folder where we place all feature framework assets used to provision SharePoint structures when a package is installed.
1. Create a folder called sharepoint to the root of the solution.
2. Create a folder called assets as a sub-folder for the just created sharepoint folder.
Your solution structure should look like the following picture:

Create feature framework files for initial deployment


To be able to provision SharePoint assets to sites with feature framework elements, we need to create needed XML files to the asset folder. Supported elements for the SharePoint
Framework solution packages are following:
Fields / site columns
Content types
List instances
List instances with custom schema
In the following steps, we define the needed structure to be provisioned.

To add an element.xml file for SharePoint definitions


1. Create a new file inside the sharepoint\assets folder named elements.xml.
2. Copy the following XML structure into elements.xml.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<Field ID="{060E50AC-E9C1-4D3C-B1F9-DE0BCAC300F6}"
Name="SPFxAmount"
DisplayName="Amount"
Type="Currency"
Decimals="2"
Min="0"
Required="FALSE"
Group="SPFx Columns" />

<Field ID="{943E7530-5E2B-4C02-8259-CCD93A9ECB18}"
Name="SPFxCostCenter"
DisplayName="Cost Center"
Type="Choice"
Required="FALSE"
Group="SPFx Columns">
<CHOICES>
<CHOICE>Administration</CHOICE>
<CHOICE>Information</CHOICE>
<CHOICE>Facilities</CHOICE>
<CHOICE>Operations</CHOICE>
<CHOICE>Sales</CHOICE>
<CHOICE>Marketing</CHOICE>
</CHOICES>
</Field>

<ContentType ID="0x010042D0C1C200A14B6887742B6344675C8B"
Name="Cost Center"
Group="SPFx Content Types"
Description="Sample content types from web part solution">
<FieldRefs>
<FieldRef ID="{060E50AC-E9C1-4D3C-B1F9-DE0BCAC300F6}" />
<FieldRef ID="{943E7530-5E2B-4C02-8259-CCD93A9ECB18}" />
</FieldRefs>
</ContentType>

<ListInstance
CustomSchema="schema.xml"
FeatureId="00bfea71-de22-43b2-a848-c05709900100"
Title="SPFx List"
Description="SPFx List"
TemplateType="100"
Url="Lists/SPFxList">
</ListInstance>

</Elements>

Note the following about the pasted XML structure:


We are provisioning two fields to the site, a content type and a list instance with custom schema.
Definitions use standard Feature Framework schema, which is well known to SharePoint developers.
Custom fields are being referenced in the introduced content type.
We use the CustomSchema attribute in the ListInstance element to define a provisioning time schema.xml file for the list. This way the list is still based on the out-of-the-box list
template (normal custom list '100' in this case), but we can define an alternative provisioning definition during initial provisioning.
When provisioning list instances using Features you must provide the ID of the Feature associated with the particular list definition. Using the FeatureId attribute you are supposed
to provide the ID of the Feature which contains the List Definition. As an example: if you’re provisioning an instance of a custom list the FeatureId attribute should be set to
{00bfea71-de22-43b2-a848-c05709900100}.
More details about the used schema structures can be found at Using Features in SharePoint Foundation on MSDN.

To add a schema.xml file for defining list structure


In the previous step, we referenced the schema.xml file in the CustomSchema attribute of the ListInstance element, so we need to include that in our package.
1. Create a new file inside the sharepoint\assets folder named schema.xml.
2. Copy the following XML structure into schema.xml.
<List xmlns:ows="Microsoft SharePoint" Title="Basic List" EnableContentTypes="TRUE" FolderCreation="FALSE" Direction="$Resources:Direction;" Url="Lists/Basic List" BaseType="0" xmlns="http://
<MetaData>
<ContentTypes>
<ContentTypeRef ID="0x010042D0C1C200A14B6887742B6344675C8B" />
</ContentTypes>
<Fields></Fields>
<Views>
<View BaseViewID="1" Type="HTML" WebPartZoneID="Main" DisplayName="$Resources:core,objectiv_schema_mwsidcamlidC24;" DefaultView="TRUE" MobileView="TRUE" MobileDefaultView="TRUE" SetupPat
<XslLink Default="TRUE">main.xsl</XslLink>
<JSLink>clienttemplates.js</JSLink>
<RowLimit Paged="TRUE">30</RowLimit>
<Toolbar Type="Standard" />
<ViewFields>
<FieldRef Name="LinkTitle"></FieldRef>
<FieldRef Name="SPFxAmount"></FieldRef>
<FieldRef Name="SPFxCostCenter"></FieldRef>
</ViewFields>
<Query>
<OrderBy>
<FieldRef Name="ID" />
</OrderBy>
</Query>
</View>
</Views>
<Forms>
<Form Type="DisplayForm" Url="DispForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="EditForm" Url="EditForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
<Form Type="NewForm" Url="NewForm.aspx" SetupPath="pages\form.aspx" WebPartZoneID="Main" />
</Forms>
</MetaData>
</List>

Note the following about the included XML structure:


The custom content type deployed by using the elements.xml file is referenced in the ContentTypeRef element.
Custom fields called SPFxAmount and SPFxCostCenter are referenced in the FieldRef element.
More details about the used schema structures can be found at Understanding Schema.xml Files article at MSDN.

Ensure that definitions are taken into use in build pipeline


Now we have created the needed structures for provisioning SharePoint assets automatically from the solution when it's deployed. The next step is to ensure that we package these XML files
as part of the solution file.
1. Open package-solution.json from the config folder.
The package-solution.json file defines the package metadata as shown in the following code:

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "asset-deployment-webpart-client-side-solution",
"id": "6690f11b-012f-4268-bc33-3086eb2dd287",
"version": "1.0.0.0",
"includeClientSideAssets": true
},
"paths": {
"zippedPackage": "solution/asset-deployment-webpart.sppkg"
}
}

2. To ensure that our newly added Feature Framework files are taken into account while the solution is being packaged, we need to include a Feature Framework feature definition for
the solution package. Let's include a JSON definition for the needed feature inside of the solution structure as demonstrated in the following code.

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "asset-deployment-webpart-client-side-solution",
"id": "6690f11b-012f-4268-bc33-3086eb2dd287",
"version": "1.0.0.0",
"includeClientSideAssets": true,
"features": [{
"title": "asset-deployment-webpart-client-side-solution",
"description": "asset-deployment-webpart-client-side-solution",
"id": "523fe887-ced5-4036-b564-8dad5c6c6e24", // <-- Update 'id' with a new GUID
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml"
],
"elementFiles":[
"schema.xml"
]
}
}]
},
"paths": {
"zippedPackage": "solution/asset-deployment-webpart.sppkg"
}
}

Note the following about the added json definitions:


Make sure you define a unique GUID for the id property inside the feature section
You can technically have multiple features in the package because features is a collection; however, this is not recommended.
elements.xml is referenced under elementManifests so that it's packaged properly for the actual feature XML structure as an element manifest file.
You can have multiple element.xml files in the definition, and they would be executed in the order they are mentioned in the JSON definition. Typically, you should avoid using multiple
element.xml files because this adds unnecessary complexity. You can define all needed assets in a single element.xml file.

Deploy and test asset provisioning


Now you are ready to deploy the solution to SharePoint. Because we are provisioning assets directly to the SharePoint sites when the solution is installed, you cannot test the capability in a
local or online Workbench.
1. In the console window, enter the following command to package your client-side solution that contains the web part so that we get the basic structure ready for packaging:

gulp bundle

2. Execute the following command to create the solution package:

gulp package-solution

The command creates the package in the sharepoint/solution folder:

asset-deployment-webpart.sppkg

3. Before testing the package in SharePoint, let's quickly have a look on the default structures created for the package around the defined feature framework elements. Move back to the
Visual Studio Code side and expand the sharepoint/solution/debug folder, which contains the raw XML structures to be included in the actual sppkg package.

4. Deploy the package that was generated to the app catalog. Go to your tenant's app catalog.
5. Upload or drag and drop the asset-deployment-webpart.sppkg located in the sharepoint/solution folder to the app catalog. SharePoint displays a dialog and asks you to trust the
client-side solution to deploy.

NOTE

SharePoint validates the published package when it's deployed and you only see the trust dialog if the package is valid for deployment. You can also see the status around this
validation from the 'Valid App Package' column in the app catalog.
6. Go to the site where you want to test the SharePoint asset provisioning. This could be any site collection in the tenant where you deployed this solution package.
7. Select the gears icon on the top nav bar on the right, and then select Add an app to go to your Apps page.
8. In the Search box, enter deployment, and then select Enter to filter your apps.
9. Select the asset-deployment-webpart-client-side-solution app to install the app on the site. When installation is completed, refresh the page by selecting F5. Notice how the
custom SPFx List has been provisioned to site as part of the solution package deployment.

10. Select SPFx List to move to the list. Notice how the custom fields Amount and Cost Center are visible automatically in the default view of the list.

Define upgrade actions for new version


Whenever you build a new version of your SharePoint Framework solution, you might have some required changes on the provisioned SharePoint assets. You can take advantage of the
Feature Framework upgrade action support when a new version of the package is being deployed.
SharePoint Framework solutions do support the following Feature Framework upgrade action definitions:
ApplyElementManifest
AddContentTypeField
 T IP

You can read more details around the Feature Framework upgrade action definitions at SharePoint Add-ins update process.

To add a new element.xml file for the new version


1. Go back to your solution in Visual Studio Code.
2. Create a new file inside the sharepoint\assets folder named elements-v2.xml.
3. Copy the following XML structure into elements-v2.xml, which defines a new SharePoint list to be provisioned with a title of New List.

<?xml version="1.0" encoding="utf-8"?>


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<ListInstance
FeatureId="00bfea71-de22-43b2-a848-c05709900100"
Title="New List"
Description="New list provisioned from v2"
TemplateType="100"
Url="Lists/NewList">
</ListInstance>

</Elements>

4. We also need a definition for actual Feature Framework upgrade actions, so create a new file inside the sharepoint\assets folder named upgrade-actions-v2.xml.
5. Copy the following XML structure into upgrade-actions-v2.xml. Notice that the feature GUID reference in the path refers to the automatically created folder under the
sharepoint/solution/debug folder and has to be updated based on your solution. This GUID also matches the GUID of the feature, which we defined in the package-solution.json
file.

<ApplyElementManifests>
<ElementManifest Location="523fe887-ced5-4036-b564-8dad5c6c6e24\elements-v2.xml" />
</ApplyElementManifests>

To deploy the new version to SharePoint


Next we need to update both the solution version and the feature version responsible for the asset provisioning.
IM P O R T A N T

The solution version indicates for SharePoint that there's a new version of the SharePoint Framework solution available. The feature version ensures that the upgrade actions are executed
accordingly when the solution package is upgraded in the existing site(s).
1. Open package-solution.json from the config folder and update the version values for both the solution and the feature to "2.0.0.0".
2. We also need to include elements-v2.xml under the elementManifest section, and need to include the upgradeActions element with a pointer to the just created upgrade-actions-
v2.xml file.
Here's a complete package-solution.json file with needed changes. Notice that identifiers for your solution could be slightly different, so concentrate on adding only the missing
pieces.

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "asset-deployment-webpart-client-side-solution",
"id": "6690f11b-012f-4268-bc33-3086eb2dd287",
"version": "2.0.0.0",
"includeClientSideAssets": true,
"features": [{
"title": "asset-deployment-webpart-client-side-solution",
"description": "asset-deployment-webpart-client-side-solution",
"id": "523fe887-ced5-4036-b564-8dad5c6c6e24",
"version": "2.0.0.0",
"assets": {
"elementManifests": [
"elements.xml",
"elements-v2.xml"
],
"elementFiles":[
"schema.xml"
],
"upgradeActions":[
"upgrade-actions-v2.xml"
]
}
}]
},
"paths": {
"zippedPackage": "solution/asset-deployment-webpart.sppkg"
}
}

IM P O R T A N T

Notice that we also included the elements-v2.xml under the elementManifest section. This ensures that when you install this package to a clean site as a version 2.0, the end result
will match the upgraded packages.
3. In the console window, enter the following command to re-package your client-side solution that contains the web part so that we get the basic structure ready for packaging:

gulp bundle

4. Execute the following command to create the solution package:

gulp package-solution

The command creates a new version of the solution package in the sharepoint/solution folder. Notice that you can easily confirm from the sharepoint/solution/debug folder that the
updated XML files are included in the solution package.
5. Next you need to deploy the new version that was generated to the app catalog. Go to your tenant's app catalog.
6. Upload or drag and drop the asset-deployment-webpart.sppkg located in the sharepoint/solution folder to the app catalog. SharePoint requests that you confirm overriding the
existing version.

7. Select Replace It to update the latest version to the app catalog.


8. Select Deploy to trust the latest version.
Notice that the App Version column for the asset-deployment-webpart-client-side-solution is now updated to be "2.0.0.0".

To update an existing instance in the site


Now that the package has been updated in the app catalog, we can move to the actual SharePoint content site and perform the upgrade for the existing instance.
1. Go to the site where you deployed the first version of the SharePoint Framework solution.
2. Go to the Site Contents page.
3. Select Details from the context menu of the asset-deployment-webpart-client-side-solution solution.

This presents the current details around the installed SharePoint Framework solution. This page also now shows the text as 'There is a new version of this app. Get it now' to indicate
that there's a new version available.

4. Select the GET IT button to start the update process for the package.

If you move to the classic experience, you can see more details on the actual upgrade action being applied for the SharePoint Framework solution.

NOTE

Because the SharePoint Framework uses the same app infrastructure as SharePoint Add-ins, the status for the upgrade indicates that the update can happen for an add-in or an app.
The update can take a while, but when the solution status changes to normal again, you can select F5 to refresh the site contents page to confirm that a new list called New List has
been successfully provisioned as part of the update process.
Now we have successfully upgraded this instance to the latest version. This Feature Framework option for SharePoint asset provisioning is pretty much the same as it is for the
SharePoint Add-in model. The key difference is that the assets are being provisioned directly to a normal SharePoint site, because there's no concept called app or add-in web with
SharePoint Framework solutions.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.

See also
Provision SharePoint assets with your solution package
Sample solution - Deployment of SharePoint assets as part of SPFx package
Deploy your SharePoint client-side web part to Azure CDN
3/26/2018 • 7 minutes to read Edit Online

Create a new sample web part and deploy its assets to an Azure Content Delivery Network (CDN) instead of using the default Office 365 CDN as the hosting solution. You'll use an Azure
Storage account integrated with a CDN to deploy your assets. SharePoint Framework build tools provide out-of-the-box support for deploying to an Azure Storage account; however, you
can also manually upload the files to your favorite CDN provider or to SharePoint.
NOTE

There are multiple different hosting options for your web part assets. This tutorial concentrates on showing the Azure CDN option, but you could also use the Office 365 CDN or simply host
your assets from SharePoint library from your tenant. In the latter case, you would not benefit from the CDN performance improvements, but that would also work from the functionality
perspective. Any location that end users can access by using HTTP would be technically suitable for hosting the assets for end users.

Configure an Azure storage account


To configure an Azure storage account and integrate it with the CDN, follow the instructions at Integrate an Azure storage account with Azure CDN, along with the detailed steps in this
article.

Storage account name


This is the name you used to create your storage account, as described in Step 1: Create a storage account.
For example, in the following screenshot, spfxsamples is the storage account name.

This creates a new storage account endpoint spfxsamples.blob.core.windows.net.


NOTE

You need to create a unique storage account name for your own SharePoint Framework projects.

BLOB container name


Create a new Blob service container. This is available in your storage account dashboard.
Select the + Container and create a new container with the following:
Name: azurehosted-webpart
Access type: Container
Storage account access key
In the storage account dashboard, select Access Key in the dashboard, and copy one of the access keys.

CDN profile and endpoint


Create a new CDN profile and associate the CDN endpoint with this BLOB container.
1. Create a new CDN profile as described in Step 2: Enable CDN for the storage account (scroll down in Step 2 for To create a new CDN profile).
For example, in the following screenshot, spfxwebparts is the CDN profile name.
2. Create a CDN endpoint as described in Step 2: Enable CDN for the storage account. The CDN endpoint is created with the following URL: http://spfxsamples.azureedge.net

For example, in the following screenshot, spfxsamples is the endpoint name, Storage is the origin type, and spfxsamples.blob.core.windows.net is the storage account.

Because you associated the CDN endpoint with your storage account, you can also access the BLOB container at the following URL: http://spfxsamples.azureedge.net/azurehosted-webpart/

Note, however, that you have not yet deployed the files.

Create a new web part project


1. Create a new project directory in your preferred location:

md azurehosted-webpart

2. Go to the project directory:

cd azurehosted-webpart

3. Create a new SharePoint Framework solution by running Yeoman SharePoint Generator:

yo @microsoft/sharepoint

4. When prompted:
Accept the default azurehosted-webpart as your solution name, and select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder for where to place the files.
Select y to use the tenant-scoped deployment option, which makes the web part available across sites immediately when it's deployed.
Select WebPart as the client-side component type to be created.
5. The next set of prompts ask for specific information about your web part:
Use AzureCDN for your web part name, and select Enter.
Accept the default AzureCDN description as your web part description, and select Enter.
Accept the default No javascript web framework as the framework you would like to use, and select Enter.

At this point, Yeoman scaffolds the solution files and installs the required dependencies. This might take a few minutes. Yeoman scaffolds the project to include your custom web part
as well.
6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Enter the following to open the web part project in Visual Studio Code:

code .

Configure the solution not to use default settings


1. Open package-solution.json in the config folder.
This is where we control the solution packaging.
2. Update includeClientSideAssets value as false so that client-side assets are NOT packaged inside of the sppkg file, which is the default behavior. Because we are hosting assets from
an external CDN, we do not want them to be included in the solution package. Your configuration should look somewhat like the following.

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "azurehosted-webpart-client-side-solution",
"id": "a4e95ed1-d096-4573-8a57-d0cc3b52da6a",
"version": "1.0.0.0",
"includeClientSideAssets": false,
"skipFeatureDeployment": true
},
"paths": {
"zippedPackage": "solution/azurehosted-webpart.sppkg"
}
}

NOTE

The skipFeatureDeployment setting is here true because the answer for the tenant-scope deployment option was said to be 'y' in the Yeoman flow. This means that you do NOT need
to explicitly install the solution to the site before the web part is available. Deploying and approving the solution package in the tenant app catalog is sufficient to make the web part
available across all the sites in the given tenant.

Configure Azure Storage account details


1. Open deploy-azure-storage.json in the config folder.
This is the file that contains your Azure Storage account details.
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/deploy-azure-storage.schema.json",
"workingDir": "./temp/deploy/",
"account": "<!-- STORAGE ACCOUNT NAME -->",
"container": "azurehosted-webpart",
"accessKey": "<!-- ACCESS KEY -->"
}

2. Replace the account, container, accessKey with your storage account name, BLOB container, and storage account access key respectively.
workingDir is the directory where the web part assets are located.
In this example, with the storage account created earlier, this file will look like:

{
"workingDir": "./temp/deploy/",
"account": "spfxsamples",
"container": "azurehosted-webpart",
"accessKey": "q1UsGWocj+CnlLuv9ZpriOCj46ikgBbDBCaQ0FfE8+qKVbDTVSbRGj41avlG73rynbvKizZpIKK9XpnpA=="
}

3. Save the file.

Configure the web part to load from CDN


For the web part to load from your CDN, you need to tell it your CDN path.
1. Switch to Visual Studio Code and open the write-manifests.json from the config folder.
2. Enter your CDN base path for the cdnBasePath property.

{
"cdnBasePath": "<!-- PATH TO CDN -->"
}

In this example, with the CDN profile created earlier, this file will look like:

{
"cdnBasePath": "https://spfxsamples.azureedge.net/azurehosted-webpart/"
}

NOTE

The CDN base path is the CDN endpoint with the BLOB container.
3. Save the file.

Prepare the web part assets to deploy


Before uploading the assets to CDN, you need to build them.
1. Switch to the console and execute the following gulp task:

gulp bundle --ship

This builds the minified assets required to upload to the CDN provider. The --ship indicates the build tool to build for distribution. You should also notice that the output of the build
tools indicate the Build Target is SHIP.

Build target: SHIP


[21:23:01] Using gulpfile ~/apps/azurehosted-webpart/gulpfile.js
[21:23:01] Starting gulp
[21:23:01] Starting 'default'...

The minified assets can be found under the temp\deploy directory.

Deploy assets to Azure Storage


1. Switch to the console of the azurehosted-webpart project directory.
2. Enter the gulp task to deploy the assets to your storage account:

gulp deploy-azure-storage

This deploys the web part bundle along with other assets such as JavaScript and CSS files to the CDN.

Deploy the updated package


To package the solution
Because you changed the web part bundle, you need to redeploy the package to the app catalog. You used --ship to generate minified assets for distribution.
1. Switch to the console of the azurehosted-webpart project directory.
2. Enter the gulp task to package the client-side solution, this time with the --ship flag set. This forces the task to pick up the CDN base path configured in the previous step:

gulp package-solution --ship

This creates the updated client-side solution package in the sharepoint\solution folder.
To upload to your app catalog
1. Upload or drag and drop the client-side solution package to the app catalog. Notice how the URL is pointing to the Azure CDN URL configured in the previous steps.
2. Select the check box to indicate that the solution can be deployed automatically across all sites in the organization.

3. Select Deploy.
The app catalog now has the client-side solution package where the web part bundle is loaded from the CDN.

Test the HelloWorld web part


1. Go to any SharePoint site in your tenant and select Add a page from the gears menu.
2. Edit the page and select AzureCDN web part from the web part picker to confirm that your deployment has been successful.

3. Notice that you are not running gulp serve, and therefore nothing is served from localhost. Content is served from the Azure CDN. You can also double-check this by selecting F12
in your browser and confirming that you can see the Azure CDN as one of the sources for the page assets.

Deploy to other CDNs


To deploy the assets to your favorite CDN provider, you can copy the files from temp\deploy folder. To generate assets for distribution, run the following gulp command as we did before
with the --ship parameter:

gulp bundle --ship

As long as you are updating the cdnBasePath accordingly, your files are being properly loaded.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.

See also
Build your first SharePoint client-side web part
Integrate web part properties with SharePoint
3/26/2018 • 4 minutes to read Edit Online

When building classic web parts, web part properties were isolated from SharePoint, and their values were managed by end-users. SharePoint Framework offers you a new set of
capabilities that simplify managing web part properties' values and integrate them with SharePoint Search. This article explains how you can use these capabilities when building SharePoint
Framework client-side web parts.
IM P O R T A N T

The following guide applies only to SharePoint Framework client-side web parts placed on modern SharePoint pages. Capabilities described in this article don't apply to classic web parts or
SharePoint Framework client-side web parts placed on classic pages.

Client-side web part properties


When building SharePoint Framework client-side web parts, you can define properties that can be configured by users. By using properties instead of fixed values, web parts are more
flexible and suitable for many different scenarios.
Compared to classic web parts, there are some differences in how the SharePoint Framework handles web part properties. The following schema illustrates how web part property values
flow through the different layers of SharePoint.

Before accepting values for web part properties from end users, you should always validate them. This not only allows you to ensure that your web parts are user-friendly, but also helps you
prevent storing invalid data in the web part's configuration.
Additionally, you should consider that the SharePoint Framework doesn't support personalization, and all users see the same configuration of the particular web part.

Specify web part property value type


In classic SharePoint web parts, web part property values were isolated from SharePoint. If you had a web part property with a URL of a file stored in SharePoint, you had to manually
ensure that this URL was valid and pointing to a correct document in case it was moved or renamed. Also, if you allowed users to enter some text to be displayed in the web part, that text
wouldn't be indexed by SharePoint Search.
When building web parts, SharePoint Framework allows you to specify what kind of value the particular web part property holds. This configuration determines how SharePoint handles the
value. Depending on the specified configuration, SharePoint can include the value of the particular property in the Search index, remove unsafe HTML, and even keep links to documents
stored in SharePoint up to date in case a file gets moved or renamed.
To specify the configuration for your web part properties, in the web part class, override the propertiesMetadata getter:

import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
IWebPartPropertiesMetadata
} from '@microsoft/sp-webpart-base';

// ...

export default class ArticleLinkWebPart extends BaseClientSideWebPart<IArticleLinkWebPartProps> {


// ...
protected get propertiesMetadata(): IWebPartPropertiesMetadata {
return {
'title': { isSearchablePlainText: true },
'intro': { isHtmlString: true },
'image': { isImageSource: true },
'url': { isLink: true }
};
}
// ...
}

The propertiesMetadata method returns an object, where the property is a string and specifies the name of the web part property, and the value is an object specifying how SharePoint
should handle that particular property.
When overriding the propertiesMetadata method, you don't have to list all the web part properties. By default, web part properties' values are not processed by SharePoint, and you
should include only those properties that you want to be processed.
Following is the list of possible values that can be set in the properties metadata and their impact on how the value of the web part property is processed by SharePoint.

ME TAD ATA V ALU E S E AR CHAB LE LINK FIX U P R EMO V E U NS AFE HTML

none (default) no no no

isSearchablePlainText yes no no

isHtmlString yes yes yes

isImageSource yes yes no

isLink yes yes no

IM P O R T A N T

When defining the configuration for your web part properties, you should use only one of the properties mentioned in the table for each web part property. Setting multiple properties will
most likely lead to undesirable results, and you should avoid it.
By default the value of a web part property is not indexed by SharePoint Search and it's not processed by SharePoint in any way. It's passed to the web part exactly how it's been entered by
the user configuring the web part.
If you specify the web part property as isSearchablePlainText , it is included in the full-text Search index. Whenever users search for any keywords included in the value of that property,
SharePoint Search returns the page with the web part in the search results. If the value contains a link to a document stored in SharePoint, that link won't be updated if the referenced
document is moved or renamed. Also, any HTML entered by users, is kept intact. When working with the value of such a property, you should treat it as plain-text and escape HTML that
might be entered by users before rendering it on the page to avoid script injection.
When a web part property is defined as isHtmlString , SharePoint first of all removes any unsafe HTML, such as script tags, from the property value. The HTML that remains can be
considered safe to render on a page. If the value contains any URLs pointing to files stored in SharePoint, as soon as one of these files is renamed or moved, SharePoint automatically
updates the URL stored in the web part property. This significantly simplifies managing URLs across all web parts and pages in your tenant. HTML web part properties are also searchable,
so users can look for any keywords included in the property value.
Property value types isImageSource and isLink are meant to be used for web part properties that include nothing else but a link to an image or a file stored in SharePoint. In both cases,
SharePoint Search includes the content in the full-text index and keeps the specified URL up to date in case the referenced file is renamed or moved. Additionally, image sources may get
additional processing to help images download faster. If the page has a title image, and the image is among the first five images on the page, or the image is in the first two rows on the page,
the image is preloaded.
Make your SharePoint client-side web part configurable
3/26/2018 • 2 minutes to read Edit Online

The property pane allows end users to configure the web part with several properties. The article Build your first web part describes how the property pane is defined in the
HelloWorldWebPart class. The property pane properties are defined in propertyPaneSettings.
A property pane has three key metadata: a page, an optional header, and at least one group.
Pages provide you the flexibility to separate complex interactions and put them into one or more pages. Pages contain a header and groups.
Headers allow you to define the title of the property pane.
Groups allow you to define the various sections or fields for the property pane through which you want to group your field sets.
The following figure shows an example of a property pane in SharePoint.

Configure the property pane


The following code example initializes and configures the property pane in your web part. You override the getPropertyPaneConfiguration method and return a collection of property
pane page(s).

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
})
]
}
]
}
]
};
}

Property pane fields


The following field types are supported:
Button
Checkbox
Choice group
Dropdown
Horizontal rule
Label
Link
Slider
Textbox
Multi-line Textbox
Toggle
Custom
The field types are available as modules in sp-client-platform. They require an import before you can use them in your code:

import {
PropertyPaneTextField,
PropertyPaneCheckbox,
PropertyPaneLabel,
PropertyPaneLink,
PropertyPaneSlider,
PropertyPaneToggle,
PropertyPaneDropdown
} from '@microsoft/sp-webpart-base';

Every field type method is defined as follows, taking PropertyPaneTextField as an example:

PropertyPaneTextField('targetProperty',{
//field properties are defined here
})

The targetProperty defines the associated object for that field type and is also defined in the props interface in your web part.
To assign types to these properties, define an interface in your web part class that includes one or more target properties.

export interface IHelloWorldWebPartProps {


targetProperty: string
}

This is then available in your web part by using this.properties.targetProperty.

<p class="ms-font-l ms-fontColor-white">${this.properties.description}</p>

When the properties are defined, you can access them in your web part by using the this.properties.[property-name]. For more information, see render method of the
HelloWorldWebPart.

Handle field changes


The property pane has two interaction modes:
Reactive
Non-reactive
In reactive mode, on every change, a change event is triggered. Reactive behavior automatically updates the web part with the new values.
While reactive mode is sufficient for many scenarios, at times you need non-reactive behavior. Non-reactive does not update the web part automatically unless the user confirms the changes.
To turn on the non-reactive mode, add the following code in your web part:

protected get disableReactivePropertyChanges(): boolean {


return true;
}

Custom property pane controls


SharePoint Framework contains a set of standard controls for the property pane. But sometimes you need additional functionality beyond the basic controls. SharePoint Framework allows
you to build custom controls to deliver the required functionality. To learn more, read the Build custom controls for the property pane guide.

See also
SharePoint Framework Overview
Configure web part icon
3/26/2018 • 4 minutes to read Edit Online

Selecting an icon that illustrates the purpose of your SharePoint client-side web part makes it easier for users to find your web part among other all web parts available in the toolbox.

Preconfigure web parts


A web part icon is defined in the web part manifest as part of preconfigured entries. If you have a multipurpose web part that can be configured to meet different needs, each configuration
can have a different icon indicating its purpose.
Using a representative icon helps users find the web part they are looking for. For more information about preconfiguring your web parts, see Simplify adding web parts with preconfigured
entries.
SharePoint Framework offers you a number of ways to define the icon for your web part.

Use Office UI Fabric icon font


One way to define the icon for your web part is by using the officeFabricIconFontName property. This property allows you to choose one of the icons offered as a part of Office UI Fabric.
For a list of available Office UI Fabric icons, see Icons.

To use a specific icon


1. On the Office UI Fabric icons overview page, copy its name, and paste it as the value of the officeFabricIconFontName property in the manifest of your web part.
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "bcae7077-85cb-41a0-b3d3-2084f268a211",
"alias": "WeatherWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "Other"
},
"title": {
"default": "Weather"
},
"description": {
"default": "Shows current weather in the given location"
},
"officeFabricIconFontName": "Sunny",
"properties": {
"location": "Munich"
}
}
]
}

2. When adding your web part to the page, the selected icon is displayed in the toolbox.

The big benefit of this approach is that you don't need to deploy the icon image file along with your web part assets. Additionally, on computers using different DPI or other accessibility
settings, the icon automatically adapts to these settings without losing quality.

Use an external icon image


Although Office UI Fabric offers many images, when building web parts you might want to use something specific to your organization to clearly separate your web parts from other first-
and third-party web parts visible in the toolbox.
In addition to using Office UI Fabric icons, SharePoint Framework also allows you to use images.

To use an image as a web part icon


1. Specify the image's absolute URL in the iconImageUrl property in the web part manifest.
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "bcae7077-85cb-41a0-b3d3-2084f268a211",
"alias": "WeatherWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "Other"
},
"title": {
"default": "Weather"
},
"description": {
"default": "Shows current weather in the given location"
},
"iconImageUrl": "https://assets.contoso.com/weather.png",
"properties": {
"location": "Munich"
}
}
]
}

2. The web part icon image displayed in the toolbox is 40x28px. If your image is bigger, it is sized proportionally to match these dimensions.

While using custom images gives you more flexibility to choose an icon for your web part, it requires you to deploy them along with your other web part assets. Additionally, your image
might lose quality when displayed in higher DPI or specific accessibility settings. To avoid quality loss, you can use vector-based SVG images, which are also supported by the SharePoint
Framework.

Use a base64-encoded image


When using a custom image, rather than specifying an absolute URL to the image file hosted together with other web part assets, you can have your image base64-encoded and use the
base64 string instead of the URL.
A number of services are available online that you can use to base64-encode your image; for more information, see Convert your images to Base64.

To use a base64-encoded image


1. Encode the image.
2. Copy the base64 string and use it as the value for the iconImageUrl property in the web part manifest.
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "bcae7077-85cb-41a0-b3d3-2084f268a211",
"alias": "WeatherWebPart",
"componentType": "WebPart",
// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,
// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"preconfiguredEntries": [
{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": {
"default": "Other"
},
"title": {
"default": "Weather"
},
"description": {
"default": "Shows current weather in the given location"
},
"iconImageUrl": "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTAyMiIgaGVpZ2h0PSI5NzgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1
"properties": {
"location": "Munich"
}
}
]
}

Base64 encoding works for both bitmap images, such as PNG, as well as vector SVG images. The big benefit of using base64-encoded images is that you don't need to deploy the web part
icon image separately.

Additional considerations
Each web part must have an icon. If you specify the web part icon by using both the officeFabricIconFontName and the iconImageUrl properties, the icon specified in the
officeFabricIconFontName is used.
If you choose not to use an Office UI Fabric icon, you have to specify a URL in the iconImageUrl property.

See also
SharePoint Framework Overview
Use web parts with the full-width column
4/10/2018 • 2 minutes to read Edit Online

Modern SharePoint pages support layouts that allow users to organize the information they present on their pages. Users can choose from a number of different section layouts such as two
columns, three columns, or one-third column. Modern pages in communication sites offer an additional section layout named Full-width column. This layout spans the full width of the
page without any horizontal margin or padding. SharePoint Framework web parts can be placed in any layout, but due to extra requirements, web parts must explicitly enable support for the
full-width column.

Layout requirements for the full-width column


One thing that regular layouts in modern SharePoint pages share, is the maximum width. To guarantee the ease of readability and usability, the body of a modern page doesn't expand
beyond a certain width. When building web parts that will be used in regular layouts, you test your web part's width against the known maximum and minimum width constraints to ensure
that they are displayed properly.
When working with the full-width column layout, however, things become a bit more complicated as that layout expands to the full width of the page. When displayed on an ultra-wide
monitor, the full-width column can even become a few thousands pixels wide. This introduces additional testing requirements that you should take into account when building web parts that
can be used in the full-width column.

Enable support for the full-width column


By default, SharePoint Framework client-side web parts cannot be placed in full-width column layouts. To allow users to add your web part to full-width columns, in the web part manifest set
the supportsFullBleed property to true .

{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "34f6d7f2-262f-460a-8b65-152d784127cb",
"alias": "HelloWorldWebPart",
"componentType": "WebPart",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"supportsFullBleed": true,

"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "HelloWorld" },
"description": { "default": "HelloWorld description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "HelloWorld"
}
}]
}

With this setting enabled, when you edit a page with a full-width column layout, your web part will be displayed among the web parts that can be added to the column.
At this time, the SharePoint Workbench doesn't support testing web parts in the full-width column layout. Instead, you will have to deploy your web part to a developer tenant, create a
communication site, and test your web part there.
Hide a web part from the toolbox
6/12/2018 • 2 minutes to read Edit Online

For scenarios where you automatically instantiate a custom web part on a modern page, you might want the web part not to be manually available for end-users. This could for example be a
status web part on a home page.

Configure hiding the web part from the toolbox


By default a SharePoint Framework client-side web part will be displayed in the web part toolbox when a user edit a page. To allow hiding the web part from the toolbox you add set the
hiddenFromToolbox property to true in the web part manifest.

{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "34f6d7f2-262f-460a-8b65-152d784127cb",
"alias": "HelloWorldWebPart",
"componentType": "WebPart",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,
"hiddenFromToolbox": true,

"preconfiguredEntries": [{
"groupId": "5c03119e-3074-46fd-976b-c60198311f70", // Other
"group": { "default": "Other" },
"title": { "default": "HelloWorld" },
"description": { "default": "HelloWorld description" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "HelloWorld"
}
}]
}

With the hiddenFromToolbox setting enabled, when you edit a page, the web part will not be listed in the toolbox.
Simplify adding web parts with preconfigured entries
5/3/2018 • 14 minutes to read Edit Online

More complex SharePoint Framework client-side web parts likely have many properties that the user must configure. You can help users by adding preconfigured property entries for
specific scenarios. A preconfigured entry initializes the web part with pre-set values.
NOTE

Before following the steps in this article, be sure to set up your SharePoint client-side web part development environment.

Web part preconfigured entries


Each SharePoint Framework client-side web part consists of two pieces:
The manifest that describes the web part
The web part code
One of the properties specified in the web part manifest is the preconfiguredEntries property.

{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",

"id": "6737645a-4443-4210-a70e-e5e2a219133a",
"alias": "GalleryWebPart",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,

"preconfiguredEntries": [{
"groupId": "1edbd9a8-0bfb-4aa2-9afd-14b8c45dd489", // Discover
"group": { "default": "Under Development" },
"title": { "default": "Gallery" },
"description": { "default": "Shows items from the selected list" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "Gallery"
}
}]
}

The preconfiguredEntries property provides information about your web part for use in the web part toolbox. When users add web parts to the page, the information from the
preconfiguredEntries property is used to display the web part in the toolbox and define its default settings when it's added to the page.
If you've built classic web parts with full-trust solutions, you can think of each entry in the preconfiguredEntries array as corresponding to a .webpart file. Just like a .webpart file, each
entry in the preconfiguredEntries property is linked to the web part code and specifies basic information about the web part, such as its title or description as well as default values for its
properties.

Properties of a preconfiguredEntries array item


Each item in the preconfiguredEntries array consists of several properties. The following table explains the purpose of each property.

PR O PER T Y NAME V ALU E T YPE R EQ U IR ED PU R PO S E S AMPLE V ALU E

title ILocalizedString yes The web part title that is displayed in "title": { "default": "Weather",
the toolbox. "nl-nl": "Weerbericht" }

description ILocalizedString yes The web part description that is "description": { "default":
displayed in the toolbox tooltips. "Shows weather in the given
location", "nl-nl": "Toont
weerbericht voor de opgegeven
locatie" }

officeFabricIconFontName string no The icon for the web part that is "officeFabricIconFontName":
displayed in the toolbox. Its value must "Sunny"
be one of the Office UI Fabric icon
names. If this property has a value, the
iconImageUrl property is ignored.

iconImageUrl string no The icon for the web part that is "iconImageUrl":
displayed in the toolbox and is "https://cdn.contoso.com/weather.png"
represented by an image URL. The
image at the URL must be exactly
40x28 px. If the
officeFabricIconName property does
not have a value, this property must
have a value.

groupId string yes The group ID to determine which "groupId": "1edbd9a8-0bfb-4aa2-


modern group contains the web part 9afd-14b8c45dd489"
in a modern site page. The SharePoint
Framework reserves group IDs for
predefined groups. The developer can
pick one from those groups. If the
developer fills an ID not in the
predefined groups, it falls back to
Other group.
PR O PER T Y NAME V ALU E T YPE R EQ U IR ED PU R PO S E S AMPLE V ALU E

group ILocalizedString no The group name in the web part picker "group": { "default": "Content",
to contain the web part in the classic "nl-nl": "Inhoud" }
page. If no value is provided, the web
part is displayed in the Miscellaneous
group.

dataVersion string no Use this field to specify the data "dataVersion": "1.0"
version of the pre-configured data
provided to the web part. Note that
data version is different from the
version field in the manifest. The
manifest version is used to control the
versioning of the web part code, while
data version is used to control the
versioning of the serialized data of the
web part. Refer to the dataVersion field
of your web part for more information.
Supported values format:
MAJOR.MINOR version

properties TProperties yes A key-value pair object with default "properties": { "location":
values for web part properties. "Redmond", "numberOfDays": 3,
"showIcon": true }

Some web part properties have a value of type ILocalizedString. This type is a key-value pair object that allows developers to specify strings for the different locales. At a minimum, a value
of type ILocalizedString must contain the default value.
Optionally, developers can provide the translations of that value to the different locales that their web part supports. If the web part is placed on a page in a locale that isn't listed in the
localized string, the default value is used instead.
Valid ILocalizedString values:

"title": {
"default": "Weather",
"nl-nl": "Weerbericht"
}

"title": {
"default": "Weather"
}

An ILocalizedString value that is not valid because the default key is missing:

"title": {
"en-us": "Weather"
}

Predefined modern groups


There are 7 out-of-the-box groups as shown in the following table. Use the group ID in the groupId property.

G R O U P NAME ID G R O U P INCLU D ES ...

Text, media, and content cf066440-0614-43d6-98ae-0b31cf14c7c3 Web parts that display text, multi-media, documents, information
from the web, and other rich content.

Discover 1edbd9a8-0bfb-4aa2-9afd-14b8c45dd489 Web parts that organize, group, and filter content to help users
discover information.

Communication and collaboration 75e22ed5-fa14-4829-850a-c890608aca2d Web parts that facilitate information sharing, team work, and social
interactions.

Planning and process 1bc7927e-4a5e-4520-b540-71305c79c20a Web parts that empower team productivity with the use of planning
and process tools.

Business and intelligence 4aca9e90-eff5-4fa1-bac7-728f5f157b66 Web parts for tracking and analyzing data, and for integrating
business flow with pages.

Site tools 070951d7-94da-4db8-b06e-9d581f1f55b1 Web parts for site information and management.

Other 5c03119e-3074-46fd-976b-c60198311f70 Web parts not in other groups.

If the developer fills an ID not in the previous list, the web part falls back to the Other group.
To see how you can use preconfigured entries when building web parts, you will build a sample gallery web part. Using several properties, users can configure this web part to show items
from a selected list in a specific way. For brevity, you will omit the actual implementation of the web part logic and will focus on using the preconfiguredEntries property to provide
preconfigured versions of the gallery web part.
Create a new web part project
1. Start by creating a new folder for your project.

md react-preconfiguredentries

2. Go to the project folder.

cd react-preconfiguredentries

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project.

yo @microsoft/sharepoint

4. When prompted, enter the following values:


react-preconfiguredentries as your solution name
Use the current folder for the location to place the files
Gallery as your web part name
Shows items from the selected list as your web part description
React as the starting point to build the web part
5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. This article uses Visual Studio Code in the steps and screenshots, but you can use any editor you prefer.

Add web part properties


In the web part manifest, add web part properties so that users can configure the gallery web part.
1. In the code editor, open the ./src/webparts/gallery/GalleryWebPart.manifest.json file.
2. Replace the properties section with the following JSON:
{
//...
"preconfiguredEntries": [{
//...
"properties": {
"listName": "",
"order": "",
"numberOfItems": 10,
"style": ""
}
}]
}

Note the following about this code:


The listName property specifies the name of the list from which list items should be displayed.
The order property specifies the order in which items should be shown, that is chronological, or reverse chronological order.
The numberOfItems property specifies how many items should be displayed.
The style property specifies how the items should be displayed, such as thumbnails, which is useful for showing images, or as a list, which is more suitable for documents.
Web part properties specified in the manifest must also be added to the web part properties interface.
3. In the code editor, open the ./src/webparts/gallery/IGalleryWebPartProps.ts file. Change its code to:

export interface IGalleryWebPartProps {


listName: string;
order: string;
numberOfItems: number;
style: string;
}

When building SharePoint Framework client-side web parts using React, after changing the web part properties interface, you need to update the web part's render method that uses
that interface to create an instance of the main React component.
4. In the code editor, open the ./src/webparts/gallery/GalleryWebPart.ts file. Change the web part render method to:

export default class GalleryWebPart extends BaseClientSideWebPart<IGalleryWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IGalleryProps> = React.createElement(Gallery, {
listName: this.properties.listName,
order: this.properties.order,
numberOfItems: this.properties.numberOfItems,
style: this.properties.style
});

ReactDom.render(element, this.domElement);
}
// ...
}

5. Update the main React component to display the values of the properties. If the web part hasn't been configured, show the standard web part placeholder. In the code editor, open the
./src/webparts/gallery/components/Gallery.tsx file, and change its code to:
import * as React from 'react';
import styles from './Gallery.module.scss';
import { IGalleryProps } from './IGalleryProps';

export default class Gallery extends React.Component<IGalleryProps, void> {


public render(): JSX.Element {
if (this.needsConfiguration()) {
return <div className="ms-Grid" style={{ color: "#666", backgroundColor: "#f4f4f4", padding: "80px 0", alignItems: "center", boxAlign: "center" }}>
<div className="ms-Grid-row" style={{ color: "#333" }}>
<div className="ms-Grid-col ms-u-hiddenSm ms-u-md3"></div>
<div className="ms-Grid-col ms-u-sm12 ms-u-md6" style={{ height: "100%", whiteSpace: "nowrap", textAlign: "center" }}>
<i className="ms-fontSize-su ms-Icon ms-Icon--ThumbnailView" style={{ display: "inline-block", verticalAlign: "middle", whiteSpace: "normal" }}></i><span className="ms-fontWeight-l
</div>
<div className="ms-Grid-col ms-u-hiddenSm ms-u-md3"></div>
</div>
<div className="ms-Grid-row" style={{ width: "65%", verticalAlign: "middle", margin: "0 auto", textAlign: "center" }}>
<span style={{ color: "#666", fontSize: "17px", display: "inline-block", margin: "24px 0", fontWeight: 100 }}>Show items from the selected list</span>
</div>
<div className="ms-Grid-row"></div>
</div>;
}
else {
return (
<div className={styles.gallery}>
<div className={styles.container}>
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className='ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1'>
<span className="ms-font-xl ms-fontColor-white">
Welcome to SharePoint!
</span>
<p className='ms-font-l ms-fontColor-white'>
Customize SharePoint experiences using web parts.
</p>
<p className='ms-font-l ms-fontColor-white'>
List: {this.props.listName}<br />
Order: {this.props.order}<br />
Number of items: {this.props.numberOfItems}<br />
Style: {this.props.style}
</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

private needsConfiguration(): boolean {


return Gallery.isEmpty(this.props.listName) ||
Gallery.isEmpty(this.props.order) ||
Gallery.isEmpty(this.props.style);
}

private static isEmpty(value: string): boolean {


return value === undefined ||
value === null ||
value.length === 0;
}
}

6. Update the main React component interface to match the web part property interface, because we are bypassing all the web part properties to this component. In the code editor,
open the ./src/webparts/gallery/components/IGalleryProps.ts file, and change its code to:

import { IGalleryWebPartProps } from '../IGalleryWebPartProps';

export interface IGalleryProps extends IGalleryWebPartProps {


}

Render web part properties in the property pane


For users to be able to use the newly defined properties to configure the web part, the properties must be displayed in the web part property pane.
1. In the code editor, open the ./src/webparts/gallery/GalleryWebPart.ts file. In the top section of the file, change the @microsoft/sp-webpart-base import statement to:

import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneDropdown,
PropertyPaneSlider,
PropertyPaneChoiceGroup
} from '@microsoft/sp-webpart-base';

2. Change the propertyPaneSettings to:


export default class GalleryWebPart extends BaseClientSideWebPart<IGalleryWebPartProps> {
// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('listName', {
label: strings.ListNameFieldLabel,
options: [{
key: 'Documents',
text: 'Documents'
},
{
key: 'Images',
text: 'Images'
}]
}),
PropertyPaneChoiceGroup('order', {
label: strings.OrderFieldLabel,
options: [{
key: 'chronological',
text: strings.OrderFieldChronologicalOptionLabel
},
{
key: 'reversed',
text: strings.OrderFieldReversedOptionLabel
}]
}),
PropertyPaneSlider('numberOfItems', {
label: strings.NumberOfItemsFieldLabel,
min: 1,
max: 10,
step: 1
}),
PropertyPaneChoiceGroup('style', {
label: strings.StyleFieldLabel,
options: [{
key: 'thumbnails',
text: strings.StyleFieldThumbnailsOptionLabel
},
{
key: 'list',
text: strings.StyleFieldListOptionLabel
}]
})
]
}
]
}
]
};
}
}

In a real-life scenario, you would retrieve the list of lists from the current SharePoint site. For brevity, in this example, you use a fixed list instead.

Add localization labels


1. In the code editor, open the ./src/webparts/gallery/loc/mystrings.d.ts file. Change its code to:

declare interface IGalleryStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
OrderFieldLabel: string;
OrderFieldChronologicalOptionLabel: string;
OrderFieldReversedOptionLabel: string;
NumberOfItemsFieldLabel: string;
StyleFieldLabel: string;
StyleFieldThumbnailsOptionLabel: string;
StyleFieldListOptionLabel: string;
}

declare module 'galleryStrings' {


const strings: IGalleryStrings;
export = strings;
}

2. Add the missing resource strings by opening the ./src/webparts/gallery/loc/en-us.js file and changing its code to:
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListNameFieldLabel": "List",
"OrderFieldLabel": "Items order",
"OrderFieldChronologicalOptionLabel": "chronological",
"OrderFieldReversedOptionLabel": "reversed chronological",
"NumberOfItemsFieldLabel": "Number of items to show",
"StyleFieldLabel": "Items display style",
"StyleFieldThumbnailsOptionLabel": "thumbnails",
"StyleFieldListOptionLabel": "list"
}
});

3. Confirm that the project is building by running the following command:

gulp serve

4. In the web browser, add the web part to the canvas and open its property pane. You should see all the properties available for users to configure.

Because you didn't specify any default values for the web part, every time users add the web part to the page, they have to configure it first. You can simplify this experience by providing
default values for the most common scenarios.

Specify default values for the web part


Imagine that users often use the gallery web part to show the five most recently added images. Rather than requiring users to configure the web part manually each time, you could provide
them with a preconfigured version that uses the correct settings.
1. In the code editor, open the ./src/webparts/gallery/GalleryWebPart.manifest.json file. Change the existing entry in the preconfiguredEntries property to:

{
// ...
"preconfiguredEntries": [{
"groupId": "6737645a-4443-4210-a70e-e5e2a219133a",
"group": { "default": "Content" },
"title": { "default": "Recent images" },
"description": { "default": "Shows 5 most recent images" },
"officeFabricIconFontName": "Picture",
"properties": {
"listName": "Images",
"order": "reversed",
"numberOfItems": 5,
"style": "thumbnails"
}
}]
}

2. Start debugging the project by running the following command:


gulp serve

NOTE

If you were debugging the project previously, stop debugging and start it again. Changes made to the web part manifest are not automatically reflected in the Workbench while
debugging, and you have to rebuild the project to see them.
3. When you open the web part toolbox to add the web part to the canvas, you see that its name and icon changed to reflect the preconfigured settings.

4. After adding the web part to the page, it works immediately by using the preconfigured settings.

Specify multiple preconfigured web part entries


Imagine that another group of users often uses your gallery web part to show documents recently added to their site. To help them use your web part, you can add another set of presets that
addresses their configuration needs.
1. In the code editor, open the ./src/webparts/gallery/GalleryWebPart.manifest.json file. Change the preconfiguredEntries property to:
{
// ...
"preconfiguredEntries": [{
"groupId": "6737645a-4443-4210-a70e-e5e2a219133a",
"group": { "default": "Content" },
"title": { "default": "Recent images" },
"description": { "default": "Shows 5 most recent images" },
"officeFabricIconFontName": "Picture",
"properties": {
"listName": "Images",
"order": "reversed",
"numberOfItems": 5,
"style": "thumbnails"
}
},
{
"groupId": "6737645a-4443-4210-a70e-e5e2a219133a",
"group": { "default": "Content" },
"title": { "default": "Recent documents" },
"description": { "default": "Shows 10 most recent documents" },
"officeFabricIconFontName": "Documentation",
"properties": {
"listName": "Documents",
"order": "reversed",
"numberOfItems": 10,
"style": "list"
}
}]
}

2. Notice how you keep the previous preconfigured entry intact and add another one next to it by using different values for properties.
3. To see the result, start debugging the project by running the following command:

gulp serve

4. When you open the web part toolbox to add the web part to the canvas, you see that there are two web parts for you to choose from.

5. After adding the Recent documents web part to the page, it works immediately by using its specific preconfigured settings.
Specify an unconfigured instance of the web part
When building web parts, there are often specific scenarios that the web part should support. Providing preconfigured entries for those scenarios makes it easier for users to use the web
part.
Depending on how you build your web part, it could be possible that the web part can support other unforeseen scenarios as well. If you only provide specific preconfigured entries, users
might not realize they can use your web part for a different scenario. It might be a good idea to provide a generic unconfigured variant of your web part as well.
1. In the code editor, open the ./src/webparts/gallery/GalleryWebPart.manifest.json file. Change the preconfiguredEntries property to:

{
// ...
"preconfiguredEntries": [{
"groupId": "6737645a-4443-4210-a70e-e5e2a219133a",
"group": { "default": "Content" },
"title": { "default": "Recent images" },
"description": { "default": "Shows 5 most recent images" },
"officeFabricIconFontName": "Picture",
"properties": {
"listName": "Images",
"order": "reversed",
"numberOfItems": 5,
"style": "thumbnails"
}
},
{
"groupId": "6737645a-4443-4210-a70e-e5e2a219133a",
"group": { "default": "Content" },
"title": { "default": "Recent documents" },
"description": { "default": "Shows 10 most recent documents" },
"officeFabricIconFontName": "Documentation",
"properties": {
"listName": "Documents",
"order": "reversed",
"numberOfItems": 10,
"style": "list"
}
},
{
"groupId": "6737645a-4443-4210-a70e-e5e2a219133a",
"group": { "default": "Content" },
"title": { "default": "Gallery" },
"description": { "default": "Shows items from the selected list" },
"officeFabricIconFontName": "CustomList",
"properties": {
"listName": "",
"order": "",
"numberOfItems": 5,
"style": ""
}
}]
}

2. Notice that the generic unconfigured version of the web part is added next to the configurations that target specific scenarios. This way, if there is no specific configuration addressing
users' needs, they can always use the generic version and configure it according to their requirements.
3. To see the result, start debugging the project by running the following command:

gulp serve

4. When you open the web part toolbox to add the web part to the canvas, you see that there are now three web parts that users can choose from.
Validate web part property values
3/26/2018 • 9 minutes to read Edit Online

When working with SharePoint Framework client-side web parts, users can configure them to meet their needs by using their properties. By validating the provided configuration values, you
can make it easier for users to configure the web part and improve the overall user experience of working with your web part.
NOTE

Before following the steps in this article, be sure to set up your development environment for building SharePoint Framework solutions.

Create a new web part project


1. Start by creating a new folder for your project.

md react-listinfo

2. Go to the project folder.

cd react-listinfo

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project.

yo @microsoft/sharepoint

4. When prompted, enter the following values:


react-listinfo as your solution name
Use the current folder for the location to place the files
React as the starting point to build the web part
List info as your web part name
Shows information about the selected list as your web part description

5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. This article uses Visual Studio Code in the steps and screenshots, but you can use any editor that you prefer.
Options for validating web part properties
SharePoint Framework offers developers two ways to validate values of web part properties. You can validate the value directly, inside a web part's code, or you can call an external API to
perform the validation there.
Validating values inline is useful for performing simple validations such as minimal/maximum length, required properties, or simple pattern recognition, like a zip code. Whenever the
validation is based on business logic, such as checking a social security number or a security group membership, calling external APIs is a better approach.
To validate the value of a web part property, you have to implement the event handler for the onGetErrorMessage event of that particular property. For inline validation, the event handler
should return a string with the validation error or an empty string if the provided value is valid.
For validation using remote APIs, the event handler returns a promise of string. If the provided value is invalid, the promise resolves with the error message. If the provided value is valid, the
promise resolves with an empty string.

Validate web part property values inline


In this step you verify that the description web part property is specified and its value is not longer than 40 characters. You do this by using the inline validation process.
1. In the code editor, open the ./src/webparts/listInfo/ListInfoWebPart.ts file. In the ListInfoWebPart class, add the validateDescription method with the following code:

export default class ListInfoWebPart extends BaseClientSideWebPart<IListInfoWebPartProps> {


// ...

private validateDescription(value: string): string {


if (value === null ||
value.trim().length === 0) {
return 'Provide a description';
}

if (value.length > 40) {


return 'Description should not be longer than 40 characters';
}

return '';
}
}

The validateDescription method checks if the description is provided, and if it isn't longer than 40 characters. If the provided description is invalid, the method returns an error
message corresponding to the validation error. If the provided value is correct, it returns an empty string.
2. Associate the validateDescription method with the description web part property. In the ListInfoWebPart class, change the implementation of the
getPropertyPaneConfiguration method to:
export default class ListInfoWebPart extends BaseClientSideWebPart<IListInfoWebPartProps> {
// ...

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel,
onGetErrorMessage: this.validateDescription.bind(this)
})
]
}
]
}
]
};
}

// ...
}

You have extended the definition of the description web part by defining the validateDescription method as the event handler for the onGetErrorMessage event.
3. Run the following command to see the result of the validation:

gulp serve

4. In the Workbench, add the web part to the canvas and open its properties. If you remove the description, you should see the first validation error.

5. Provide a value that's longer than 40 characters. You should see another validation error displayed under the text field.
6. Notice that when providing an invalid value, the web part is rendered showing the last valid value. Additionally, in the non-reactive property pane mode, if one of the web part
properties is invalid, the Apply button is disabled, preventing the user from applying the invalid configuration.

Validate web part property values using remote APIs


In some scenarios, validating web part property values can be more complex and may require specific business logic. In such cases, it might be more efficient for you to validate the value by
using an existing API rather than implementing and maintaining the business logic in the web part.
In this step, you implement validation logic that checks if the list with the name specified in the web part properties exists in the current SharePoint site.

Add the listName web part property


1. In the code editor, open the ./src/webparts/listInfo/ListInfoWebPart.manifest.json file. In the properties property, add a new property named listName with the default value
set to an empty string:
{
"$schema": "https://dev.office.com/json-schemas/spfx/client-side-web-part-manifest.schema.json",
"id": "1ec8f92d-ea55-4584-bf50-bac435c916bf",
"alias": "ListInfoWebPart",
"componentType": "WebPart",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,

"preconfiguredEntries": [{
"groupId": "1ec8f92d-ea55-4584-bf50-bac435c916bf",
"group": { "default": "Under Development" },
"title": { "default": "List info" },
"description": { "default": "Shows information about the selected list" },
"officeFabricIconFontName": "Page",
"properties": {
"description": "List info"
}
}]
}

2. In the code editor, open the ./src/webparts/listInfo/IListInfoWebPartProps.ts file, and extend the interface definition with the listName property of type string.

export interface IListInfoWebPartProps {


description: string;
listName: string;
}

3. Finish adding the new web part property by opening the ./src/webparts/listInfo/ListInfoWebPart.ts file in the code editor, and changing the implementation of the
getPropertyPaneConfiguration method to:

export default class ListInfoWebPart extends BaseClientSideWebPart<IListInfoWebPartProps> {


// ...

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel,
onGetErrorMessage: this.validateDescription.bind(this)
}),
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}

// ...
}

4. Add the missing ListNameFieldLabel resource string by changing the code of the ./src/webparts/listInfo/loc/mystrings.d.ts file to:

declare interface IListInfoStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
DescriptionFieldLabel: string;
ListNameFieldLabel: string;
}

declare module 'listInfoStrings' {


const strings: IListInfoStrings;
export = strings;
}

5. Change the code of the ./src/webparts/listInfo/loc/en-us.js file to:

define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"DescriptionFieldLabel": "Description Field",
"ListNameFieldLabel": "List name"
}
});

6. Run the following command to verify that the project is running and that the newly added list name property is displayed in the web part property pane:
gulp serve

Validate the name of the list by using the SharePoint REST API
In this step, you validate the provided list name and check if it corresponds to an existing list on the current SharePoint site.
1. In the code editor, open the ./src/webparts/listInfo/ListInfoWebPart.ts file, and add the following references:

import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';


import { escape } from '@microsoft/sp-lodash-subset';

2. In the ListInfoWebPart class, add the validateListName method with the following code:

export default class ListInfoWebPart extends BaseClientSideWebPart<IListInfoWebPartProps> {


// ...

private validateListName(value: string): Promise<string> {


return new Promise<string>((resolve: (validationErrorMessage: string) => void, reject: (error: any) => void): void => {
if (value === null ||
value.length === 0) {
resolve('Provide the list name');
return;
}

this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + `/_api/web/lists/getByTitle('${escape(value)}')?$select=Id`, SPHttpClient.configurations.v1)


.then((response: SPHttpClientResponse): void => {
if (response.ok) {
resolve('');
return;
}
else if (response.status === 404) {
resolve(`List '${escape(value)}' doesn't exist in the current site`);
return;
}
else {
resolve(`Error: ${response.statusText}. Please try again`);
return;
}
})
.catch((error: any): void => {
resolve(error);
});
});
}
}

First, the validateListName method checks if a list name has been provided. If not, it resolves the promise with a relevant validation error. If the user has provided a list name, the
validateListName method uses the SPHttpClient to call the SharePoint REST API and check if the list with the specified name exists.
If the list with the specified name exists on the current site, the response returns a 200 OK status code, and the validateListName method resolves the promise with an empty string,
confirming that the provided value represents a valid list.
If the list with the specified name doesn't exist, the response returns a different code. Typically, it is a 404 Not Found response, but if the request failed in some other way, a different
status code can be returned. In both cases, the validateListName method displays a relevant error message to the user.
With the list name validation method defined, the next step is to configure it as the validation handler for the listName web part property.
3. In the ListInfoWebPart class, replace the code of the getPropertyPaneConfiguration method with:
export default class ListInfoWebPart extends BaseClientSideWebPart<IListInfoWebPartProps> {
// ...

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel,
onGetErrorMessage: this.validateDescription.bind(this)
}),
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel,
onGetErrorMessage: this.validateListName.bind(this)
})
]
}
]
}
]
};
}

// ...
}

4. Run the following command to see the result of the validation:

gulp serve --nobrowser

Because the list name validation method communicates with the SharePoint REST API, you have to test the web part in the hosted version of the SharePoint workbench.
5. Add the web part to the canvas and open its properties. Because you haven't specified a default value for the list name, which is a required property, you see a validation error.

If you provide the name of a list that doesn't exist, the web part displays a validation error stating that the list you specified doesn't exist in the current site.
If you specify the name of an existing list, the validation error disappears.

Optimize validation using remote APIs


When validating web part properties using remote APIs, SharePoint Framework monitors changes in the property pane controls and sends updated values for validation to the specified
validation handler. By default, the SharePoint Framework waits 200 ms before triggering the validation process. If the user hasn't changed the particular value for 200 ms, the SharePoint
Framework starts the validation process. When the validation handler uses a remote API, each time the validation process starts, that method issues a web request to the API to validate the
specified value. If users don't type fast enough, this results in partially completed values being sent over for validation, unnecessarily stressing the network and the API. In such cases, you
should consider increasing the validation delay.
You can configure the validation delay for each property separately, depending on the type of value that users need to provide.
To increase the validation delay for the listName property
1. In the code editor, open the ./src/webparts/listInfo/ListInfoWebPart.ts file. Change the code of the getPropertyPaneConfiguration method to:

export default class ListInfoWebPart extends BaseClientSideWebPart<IListInfoWebPartProps> {


// ...

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel,
onGetErrorMessage: this.validateDescription.bind(this)
}),
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel,
onGetErrorMessage: this.validateListName.bind(this),
deferredValidationTime: 500
})
]
}
]
}
]
};
}

// ...
}

2. The deferredValidationTime property specifies the number of milliseconds that the SharePoint Framework waits before starting the validation process.
3. Run the following command to see that the applied delay is working as expected:

gulp serve --nobrowser


Share data between client-side web parts
3/26/2018 • 9 minutes to read Edit Online

When building client-side web parts, loading data once and reusing it across different web parts helps you improve the performance of your pages and decrease the load on your network.
This article describes a number of approaches that you can use to share data across multiple web parts.

Why share data between web parts


Often, when building web parts, a number of them are used together on one page. If you consider each web part as a standalone part of the page, you may end up in a situation where you
are loading a similar or even the same set of data multiple times on the same page. This unnecessarily slows down the loading of the page and increases traffic on your network.

A sample service responsible for loading the data could look like the following:
import { IDocument } from './IDocument';

export class DocumentsService {


public static getRecentDocument(): Promise<IDocument> {
return new Promise<IDocument>((resolve: (document: IDocument) => void, reject: (error: any) => void): void => {
// [...] reach out to a remote API
resolve(recentDocument);
});
}

public static getRecentDocuments(startFrom: number = 0): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (documents: IDocument[]) => void, reject: (error: any) => void): void => {
// [...] reach out to a remote API
resolve(recentDocuments);
});
}
}

SharePoint Framework client-side web parts would consume this service by using the following code:

import { DocumentsService, IDocument } from '../../services';

export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {

public render(): void {


this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'documents');

DocumentsService.getRecentDocuments(this.properties.startFrom)
.then((documents: IDocument[]): void => {
const element: React.ReactElement<IRecentDocumentsProps> = React.createElement(
RecentDocuments,
{
documents: documents
}
);

this.context.statusRenderer.clearLoadingIndicator(this.domElement);
ReactDom.render(element, this.domElement);
});
}

// ...
}

To improve the loading time of the page and decrease the traffic on your network, you can build your web parts in such a way that they load the data only once. Whenever one of the web
parts on the page requests a specific set of data, it reuses data loaded previously if possible.

Store the retrieved data in a globally-scoped variable


NOTE

Globally-scoped variables are generally a bad idea. However, for illustration and simplicity purposes, we are using them here as "demo code." There are many patterns around this issue,
including importing/exporting modules by using TypeScript concepts.
Web parts built using the SharePoint Framework are isolated into separate modules. This way, one web part cannot directly access data and properties stored by another web part. One way
to overcome this design characteristic and make data loaded by one web part available to other web parts on the page is to assign the retrieved data to a globally-scoped variable.
Using the data access service shown earlier, it could be changed as follows:
import { IDocument } from './IDocument';

export class DocumentsService {


public static getRecentDocument(): Promise<IDocument> {
return new Promise<IDocument>((resolve: (document: IDocument) => void, reject: (error: any) => void): void => {
this.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments[0]);
});
});
}

public static getRecentDocuments(startFrom: number = 0): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (documents: IDocument[]) => void, reject: (error: any) => void): void => {
this.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments.slice(startFrom, startFrom + 3));
});
});
}

private static ensureRecentDocuments(): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (recentDocuments: IDocument[]) => void, reject: (error: any) => void): void => {
if ((window as any).loadedData) {
// data already loaded so reuse
resolve((window as any).loadedData);
return;
}

if ((window as any).loadingData) {
// data is being loaded, wait a moment and try again
window.setTimeout((): void => {
DocumentsService.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments);
});
}, 100);
}
else {
(window as any).loadingData = true;
// data not loaded yet, call the remote API,
// store the data for subsequent requests, and resolve the Promise
(window as any).loadedData = loadedData;
(window as any).loadingData = false;
resolve((window as any).loadedData);
}
});
}
}

Notice how loading the data has been moved from the specific methods to the ensureRecentDocuments method. If the data has been loaded previously, the method resolves the promise
immediately by returning the previously loaded documents. If the data is currently being loaded, the method waits for 100 ms and tries resolving the promise again.
If you look at the log in the developer tools, you notice that the remote API is now called only once.
Looking at the informational messages, you can confirm that when the second web part tries to load the data, it detects that the data is already being loaded. After the data is loaded, it reuses
the existing data rather than loading it itself.
Using a globally-scoped variable is the easiest way to exchange data between different web parts on the page. One downside of using this approach, however, is that the data is exposed not
only to web parts but also to all other elements on the page. This introduces the risk of other elements on the page using the same variable as you to store their data, potentially overwriting
your data.

Store the retrieved data in a cookie


Another approach to exchange data across different web parts is by storing the data in a cookie. The added benefit of using cookies is that you can persist the data for longer periods of time.
So in cases where the data doesn't change often, you can load the data once and reuse it not only across different web parts but also across different pages.
The implementation that uses cookies is very similar to how you store data in a globally-scoped variable. The only difference is that the actual data would be stored in a cookie.
import { IDocument } from './IDocument';
import * as Cookies from 'js-cookie';

export class DocumentsService {


private static cookieName: string = 'recentDocuments';

public static getRecentDocument(): Promise<IDocument> {


return new Promise<IDocument>((resolve: (document: IDocument) => void, reject: (error: any) => void): void => {
this.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments[0]);
});
});
}

public static getRecentDocuments(startFrom: number = 0): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (documents: IDocument[]) => void, reject: (error: any) => void): void => {
this.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments.slice(startFrom, startFrom + 3));
});
});
}

private static ensureRecentDocuments(): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (recentDocuments: IDocument[]) => void, reject: (error: any) => void): void => {
let loadedData: IDocument[] = Cookies.getJSON(DocumentsService.cookieName);
if (loadedData) {
// data already loaded so reuse
resolve(loadedData);
return;
}

if ((window as any).loadingData) {
// data is being loaded, wait a moment and try again
window.setTimeout((): void => {
DocumentsService.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments);
});
}, 100);
}
else {
(window as any).loadingData = true;
// data not loaded yet, call the remote API,
// store the data for subsequent requests, and resolve the Promise
Cookies.set(DocumentsService.cookieName, loadedData, {
expires: 1,
path: '/'
});
(window as any).loadingData = false;
resolve(loadedData);
}
});
}
}

In the previous example, the js-cookie package is used to simplify working with cookies. Using the parameters passed into the Cookies.set() method, you can specify to which pages and for
how long the retrieved data should be available.
When you load the page in Microsoft Edge for the first time, you see that the data is retrieved once and reused by both web parts.
On subsequent requests, a web part can directly reuse the previously loaded data without calling the remote API.
When loading the page in Google Chrome, you see that the data is loaded twice from the remote API and is not being cached at all.
Different web browsers have different limits regarding how much data can be stored in a cookie. In this example, the retrieved data exceeds the maximum length of what can be stored in a
cookie in Google Chrome. As a result, no cookie is being set and the data is loaded twice.
While you could use cookies to share data between web parts, assuming the data that you want to share is not too large, there are some downsides to using cookies. Similar to globally-
scoped variables, cookies are available to all elements on the page, or even across the whole portal, and could be overwritten by them. Additionally, web browsers send cookies with outgoing
requests and retrieve them with incoming responses, which adds overhead to loading information in the portal. Another important thing that you should consider is that cookies are
persisted in the web browser, and you should never store any confidential information in them.

Store the retrieved data in session or local storage


An alternative to using cookies is storing data in session or local storage. Similar to cookies, browser storage allows you to persist data not only for subsequent requests but also across
pages. The benefits of using browser storage over using cookies are that data stored in browser storage is not sent with outgoing requests and you can store more data than in a cookie.
Comparable to cookies, browser storage is capable of storing only string values. So if you need to store more complex objects, you need to serialize them first by using, for example, the
native JSON.stringify() method.
If you want data to be stored only during the current session, you should use session storage. If the data should be persisted for a longer period of time, you should use local storage. Data
stored in local storage doesn't expire and it's up to you to clear it.
The previously used implementation of the data access service based on cookies can easily be adapted to use local storage instead.
import { IDocument } from './IDocument';

export class DocumentsService {


private static storageKey: string = 'recentDocuments';

public static getRecentDocument(): Promise<IDocument> {


return new Promise<IDocument>((resolve: (document: IDocument) => void, reject: (error: any) => void): void => {
this.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments[0]);
});
});
}

public static getRecentDocuments(startFrom: number = 0): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (documents: IDocument[]) => void, reject: (error: any) => void): void => {
this.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments.slice(startFrom, startFrom + 3));
});
});
}

private static ensureRecentDocuments(): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (recentDocuments: IDocument[]) => void, reject: (error: any) => void): void => {
let loadedData: IDocument[] = localStorage ? JSON.parse(localStorage.getItem(DocumentsService.storageKey)) : undefined;
if (loadedData) {
// data already loaded so reuse
resolve(loadedData);
return;
}

if ((window as any).loadingData) {
// data is being loaded, wait a moment and try again
window.setTimeout((): void => {
DocumentsService.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments);
});
}, 100);
}
else {
(window as any).loadingData = true;
// data not loaded yet, call the remote API,
// store the data for subsequent requests, and resolve the Promise
if (localStorage) {
localStorage.setItem(DocumentsService.storageKey, JSON.stringify(loadedData));
}
(window as any).loadingData = false;
resolve(loadedData);
}
});
}
}

The implementation of this service is very similar to when using cookies. One thing that you should keep in mind, however, is that browser storage can be disabled by the user, and you
should always check for its availability before performing operations on it. Just as with cookies, local storage is persisted in the web browser and you should never use it to store any
confidential information.

See also
Tutorial: Share data between web parts by using a global variable
Share data between web parts by using a global variable (tutorial)
5/3/2018 • 18 minutes to read Edit Online

When building client-side web parts, loading data once and reusing it across different web parts helps improve the performance of your pages and decrease the load on your network.
NOTE

Before following the steps in this article, be sure to set up your SharePoint client-side web part development environment.

Create a new project


1. Using a command prompt, create a new folder for your project:

md react-recentdocuments

2. Go into the project folder:

cd react-recentdocuments

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, use the following values:


WebPart as the type of client-side component to create.
react-recentdocuments as your solution name.
Use the current folder for the location to place the files.
Recent documents as your web part name.
Shows recently modified documents as your web part description.
React as the framework to use.

5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. This article uses Visual Studio Code in the steps and screenshots, but you can use any editor that you prefer.
Show the recently modified documents
The Recent Documents web part shows information about the most recently modified documents displayed as cards by using Office UI Fabric.

Remove the standard description property


1. Remove the standard description property from the IRecentDocumentsWebPartProps interface. In the code editor, open the
./src/webparts/recentDocuments/IRecentDocumentsWebPartProps.ts file, and paste the following code:

export interface IRecentDocumentsWebPartProps {


}

2. Remove the standard description property from the web part manifest. Open the ./src/webparts/recentDocuments/RecentDocumentsWebPart.manifest.json file, and from
the properties property, remove the description property:

{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",

"id": "7a7e3aa9-5d8a-4155-936b-0b0e06e9ca11",
"alias": "RecentDocumentsWebPart",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,

"preconfiguredEntries": [{
"groupId": "7a7e3aa9-5d8a-4155-936b-0b0e06e9ca11",
"group": { "default": "Under Development" },
"title": { "default": "Recent documents" },
"description": { "default": "Shows recently modified documents" },
"officeFabricIconFontName": "Page",
"properties": {
}
}]
}

3. Remove the standard description property from the web part. In the code editor, open the ./src/webparts/recentDocuments/RecentDocumentsWebPart.ts file, and replace its
render method with the following code:

export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IRecentDocumentsProps > = React.createElement(
RecentDocuments,
{
}
);

ReactDom.render(element, this.domElement);
}
// ...
}

4. Replace its getPropertyPaneConfiguration method with the following code:

export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {


// ...

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: []
}
]
}
]
};
}
}

Create the IDocumentActivity interface


Use this interface to display the activity information of a particular document on a card.
In the ./src/webparts/recentDocuments folder, create a new file named IDocumentActivity.ts, and paste the following code:

export interface IDocumentActivity {


title: string;
actorName: string;
actorImageUrl: string;
}

Create the IDocument interface


This interface represents a document with all the information necessary to display the document as a card.
In the ./src/webparts/recentDocuments folder, create a new file named IDocument.ts, and paste the following code:
import { IDocumentActivity } from './IDocumentActivity';

export interface IDocument {


title: string;
url: string;
imageUrl: string;
iconUrl: string;
activity: IDocumentActivity;
}

Show recent documents in the RecentDocuments React component


1. Add the documents property to the IRecentDocumentsProps interface. In the code editor, open the
./src/webparts/recentDocuments/components/IRecentDocumentsProps.ts file, and paste the following code:

import { IDocument } from '../IDocument';

export interface IRecentDocumentsProps {


documents: IDocument[];
}

2. In the code editor, open the ./src/webparts/recentDocuments/components/RecentDocuments.tsx file, and paste the following code:

import * as React from 'react';


import {
DocumentCard,
DocumentCardType,
DocumentCardPreview,
DocumentCardTitle,
DocumentCardActivity
} from 'office-ui-fabric-react';
import { IDocument } from '../IDocument';
import styles from './RecentDocuments.module.scss';
import { IRecentDocumentsProps } from './IRecentDocumentsProps';

export default class RecentDocuments extends React.Component<IRecentDocumentsProps, void> {


public render(): React.ReactElement<IRecentDocumentsProps> {
const documents: JSX.Element[] = this.props.documents.map((document: IDocument, index: number, array: IDocument[]): JSX.Element => {
return (
<DocumentCard type={DocumentCardType.compact} onClickHref={document.url} accentColor='#ce4b1f' key={index}>
<DocumentCardPreview previewImages={[{
name: document.title,
url: document.url,
previewImageSrc: document.imageUrl,
iconSrc: document.iconUrl,
width: 144
}]} />
<div className='ms-DocumentCard-details'>
<DocumentCardTitle
title={document.title}
shouldTruncate={true} />
<DocumentCardActivity
activity={document.activity.title}
people={
[
{ name: document.activity.actorName, profileImageSrc: document.activity.actorImageUrl }
]
}
/>
</div>
</DocumentCard>
);
});
return (
<div className={styles.helloWorld}>
{documents}
</div>
);
}
}

First, the component iterates through the documents passed by using its documents property. For each document, it builds an Office UI Fabric DocumentCard, filling its properties with the
relevant information about that particular document. Finally, when cards for all documents have been built, the component adds them to its body and returns the complete markup.

Load the information about the recent documents


In this example, the information about the recently modified documents is loaded from a static data set. You could, however, easily change this implementation to load the data from a
SharePoint document library instead.
1. In the code editor, open the ./src/webparts/recentDocuments/RecentDocumentsWebPart.ts file. Add an import statement for the IDocument interface under the other import
statements at the top of the file by using the following code:

import { IDocument } from './IDocument';

2. In the RecentDocumentsWebPart class, add a new private variable named documents by using the following code:
export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {
private static documents: IDocument[] = [
{
title: 'Proposal for Jacksonville Expansion Ad Campaign',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BCBF65183-0378-485B-AB67-791E0FC81D72%7D&file=Jacksonville%2
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BCBF6
iconUrl: '',
activity: {
title: 'Modified, January 25 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Customer Feedback for ZT1000',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7B5449CE24-BFB7-442E-843D-E0C86CEB71CC%7D&file=Customer%20Fee
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7B5449
iconUrl: '',
activity: {
title: 'Modified, January 23 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Asia Q3 Marketing Overview',
url: 'https://contoso-my.sharepoint.com/personal/alexw_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BFD077A94-AB7D-45F9-A810-36229E518A94%7D&file=Asia%20Q3%20Mark
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=18231116-2bf0-474c-93ee-eb362681b293&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BFD07
iconUrl: '',
activity: {
title: 'Modified, January 23 2017',
actorName: 'Alex Wilber',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Trey Research Business Development Plan',
url: 'https://contoso.sharepoint.com/sites/contoso/Resources/Document%20Center/_layouts/15/WopiFrame.aspx?sourcedoc=%7B743A6C44-D3F8-4ECC-A1B7-EA9844911314%7D&file=Trey%20Research%
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=923a6ce1-7b67-4bd0-a59f-89d37f233804&guidWeb=c12486eb-661c-46c7-baba-073a8a45b610&guidFile=%7B743A
iconUrl: '',
activity: {
title: 'Modified, January 15 2017',
actorName: 'Alex Wilber',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'XT1000 Marketing Analysis',
url: 'https://contoso-my.sharepoint.com/personal/henriettam_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BA8B9F935-E5A1-47AD-B052-D5ED30E682AB%7D&file=XT1000%20Ma
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=b187e1dd-7687-49e0-87ff-6250e61e56ac&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BA8B9
iconUrl: '',
activity: {
title: 'Modified, December 15 2016',
actorName: 'Henrietta Mueller',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
}
];

// ...
}

3. Change the render method to load and render the information about the recently modified documents:

export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {


// ...
public render(): void {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'documents');

window.setTimeout((): void => {


const element: React.ReactElement<IRecentDocumentsProps> = React.createElement(
RecentDocuments,
{
documents: RecentDocumentsWebPart.documents.slice(0, 3)
}
);

this.context.statusRenderer.clearLoadingIndicator(this.domElement);
ReactDom.render(element, this.domElement);
}, 300);
}
// ...
}

4. Verify that the web part is working correctly and shows information about the three most recently modified documents by running the following command from a command prompt
in your project directory:

gulp serve

5. In the SharePoint Workbench, add the Recent Documents web part to the canvas.
Show the most recently modified document
The Recent Document web part shows information about the most recently modified document.

Add the second web part


To illustrate sharing data between web parts, add a second web part to the project.
1. Using a command prompt in the project folder, run the SharePoint Framework Yeoman generator.
yo @microsoft/sharepoint

2. When prompted, enter the following values:


WebPart as the type of client-side component to create.
Recent document as your web part name.
Shows information about the most recently modified document as your web part description.

Remove the standard description property


1. Remove the description property from the IRecentDocumentWebPartProps interface. In the code editor, open the
./src/webparts/recentDocument/IRecentDocumentWebPartProps.ts file, and paste the following code:

export interface IRecentDocumentWebPartProps {


}

2. Remove the standard description property from the web part manifest. Open the ./src/webparts/recentDocument/RecentDocumentWebPart.manifest.json file, and from the
properties property, remove the description property:

{
"$schema": "../../../node_modules/@microsoft/sp-module-interfaces/lib/manifestSchemas/jsonSchemas/clientSideComponentManifestSchema.json",

"id": "71a6f643-1ac1-47ee-a9f1-502ef52f26d4",
"alias": "RecentDocumentWebPart",
"componentType": "WebPart",
"version": "0.0.1",
"manifestVersion": 2,

"preconfiguredEntries": [{
"groupId": "71a6f643-1ac1-47ee-a9f1-502ef52f26d4",
"group": { "default": "Under Development" },
"title": { "default": "Recent document" },
"description": { "default": "Shows information about the most recently modified document" },
"officeFabricIconFontName": "Page",
"properties": {
}
}]
}

3. Remove the standard description property from the web part property pane. In the code editor, open the ./src/webparts/recentDocument/RecentDocumentWebPart.ts file, and
replace its render method with the following code:

export default class RecentDocumentWebPart extends BaseClientSideWebPart<IRecentDocumentWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IRecentDocumentProps> = React.createElement(
RecentDocument,
{
}
);

ReactDom.render(element, this.domElement);
}
// ...
}

4. Replace its getPropertyPaneConfiguration method with the following code:


export default class RecentDocumentWebPart extends BaseClientSideWebPart<IRecentDocumentWebPartProps> {
// ...

protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {


return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: []
}
]
}
]
};
}
}

Reuse the IDocument and IDocumentActivity interfaces


The Recent Document web part displays information about the most recently modified document in a different way than the Recent Documents web part, but both web parts use the same
data structure representing a document. Instead of duplicating the IDocument and IDocumentActivity interfaces, you can reuse them across both web parts.
1. In Visual Studio Code, activate the Explorer pane, and from the ./src/webparts/recentDocuments folder, move the IDocument.ts and IDocumentActivity.ts files one level up, to
the ./src/webparts folder.

Having moved the files to another location in your project, you need to update the paths where they're referenced.
2. In the code editor, open the ./src/webparts/recentDocuments/components/IRecentDocumentsProps.ts file, and change its code to:

import { IDocument } from '../../IDocument';

export interface IRecentDocumentsProps {


documents: IDocument[];
}

3. Open the ./src/webparts/recentDocuments/components/RecentDocuments.tsx file, and update the import statement of the IDocument interface to:

import { IDocument } from '../../IDocument';

4. Open the ./src/webparts/recentDocuments/RecentDocumentsWebPart.ts file, and update the import statement of the IDocument interface to:

import { IDocument } from '../IDocument';

Show the most recent document in the RecentDocument React component


1. Add the document property to the IRecentDocumentProps interface. In the code editor, open the ./src/webparts/recentDocument/components/IRecentDocumentProps.ts file,
and paste the following code:

import { IDocument } from '../../IDocument';

export interface IRecentDocumentProps {


document: IDocument;
}

2. In the code editor, open the ./src/webparts/recentDocument/components/RecentDocument.tsx file, and paste the following code:

import * as React from 'react';


import {
DocumentCard,
DocumentCardPreview,
DocumentCardTitle,
DocumentCardActivity,
ImageFit
} from 'office-ui-fabric-react';
import { IDocument } from '../../IDocument';
import styles from './RecentDocument.module.scss';
import { IRecentDocumentProps } from './IRecentDocumentProps';

export default class RecentDocument extends React.Component<IRecentDocumentProps, void> {


public render(): React.ReactElement<IRecentDocumentProps> {
const document: IDocument = this.props.document;

return (
<div className={styles.helloWorld}>
<DocumentCard onClickHref={document.url}>
<DocumentCardPreview previewImages={[{
name: document.title,
url: document.url,
previewImageSrc: document.imageUrl,
iconSrc: document.iconUrl,
imageFit: ImageFit.cover,
width: 318,
height: 196,
accentColor: '#ce4b1f'
}]} />
<DocumentCardTitle
title={document.title}
shouldTruncate={true} />
<DocumentCardActivity
activity={document.activity.title}
people={
[
{ name: document.activity.actorName, profileImageSrc: document.activity.actorImageUrl }
]
}
/>
</DocumentCard>
</div>
);
}
}

The RecentDocument React component uses the information about the most recently modified document passed in the document property to render an Office UI Fabric DocumentCard.

Load the information about the recent document


In this example, the information about the most recently modified document is loaded from a static data set. You could, however, easily change this implementation to load the data from a
SharePoint document library instead.
1. In the code editor, open the ./src/webparts/recentDocument/RecentDocumentWebPart.ts file. Add an import statement for the IDocument interface under the other import
statements at the top of the file by using the following code:

import { IDocument } from '../IDocument';

2. In the RecentDocumentWebPart class, add a new private variable named document by using the following code:

export default class RecentDocumentWebPart extends BaseClientSideWebPart<IRecentDocumentWebPartProps> {


private static document: IDocument = {
title: 'Proposal for Jacksonville Expansion Ad Campaign',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BCBF65183-0378-485B-AB67-791E0FC81D72%7D&file=Jacksonville%20Ad%
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BCBF65183
iconUrl: '',
activity: {
title: 'Modified, January 25 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
};

// ...
}

3. Change the render method to load and render the information about the most recently modified document:
export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {
// ...
public render(): void {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'documents');

window.setTimeout((): void => {


const element: React.ReactElement<IRecentDocumentProps> = React.createElement(
RecentDocument,
{
document: RecentDocumentWebPart.document
}
);

this.context.statusRenderer.clearLoadingIndicator(this.domElement);
ReactDom.render(element, this.domElement);
}, 300);
}
// ...
}

4. Verify that the web part is working correctly and shows information about the most recently modified document by running the following command from a command prompt in your
project folder:

gulp serve

5. In the SharePoint Workbench, add the Recent Document web part to the canvas.

The current implementation is a typical example of two web parts being developed independently. If they were both placed on the same page and were loading data from SharePoint, they
would execute two separate requests to retrieve similar information. If, at some point, you had to change where the information about the recently modified documents is loaded from, you
would have to update both web parts.
To improve the performance of loading the page and simplify maintaining the web part code, you can centralize the logic of retrieving the data and make the once retrieved data available to
both web parts.

Centralize loading data


To centralize loading the information about recently modified documents, build a service that is referenced by both web parts.

Move the data model interfaces


1. In the project folder, create the ./src/services/documentsService folder path.
2. From the ./src/webparts folder, move the IDocument.ts and IDocumentActivity.ts files to the ./src/services/documentsService folder.
Build the data access service
In the ./src/services/documentsService folder, create a new file named DocumentsService.ts, and paste the following code:
import { IDocument } from './IDocument';

export class DocumentsService {


private static documents: IDocument[] = [
{
title: 'Proposal for Jacksonville Expansion Ad Campaign',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BCBF65183-0378-485B-AB67-791E0FC81D72%7D&file=Jacksonville%20Ad
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BCBF6518
iconUrl: '',
activity: {
title: 'Modified, January 25 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Customer Feedback for ZT1000',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7B5449CE24-BFB7-442E-843D-E0C86CEB71CC%7D&file=Customer%20Feedba
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7B5449CE2
iconUrl: '',
activity: {
title: 'Modified, January 23 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Asia Q3 Marketing Overview',
url: 'https://contoso-my.sharepoint.com/personal/alexw_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BFD077A94-AB7D-45F9-A810-36229E518A94%7D&file=Asia%20Q3%20Marketi
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=18231116-2bf0-474c-93ee-eb362681b293&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BFD077A9
iconUrl: '',
activity: {
title: 'Modified, January 23 2017',
actorName: 'Alex Wilber',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Trey Research Business Development Plan',
url: 'https://contoso.sharepoint.com/sites/contoso/Resources/Document%20Center/_layouts/15/WopiFrame.aspx?sourcedoc=%7B743A6C44-D3F8-4ECC-A1B7-EA9844911314%7D&file=Trey%20Research%20B
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=923a6ce1-7b67-4bd0-a59f-89d37f233804&guidWeb=c12486eb-661c-46c7-baba-073a8a45b610&guidFile=%7B743A6C4
iconUrl: '',
activity: {
title: 'Modified, January 15 2017',
actorName: 'Alex Wilber',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'XT1000 Marketing Analysis',
url: 'https://contoso-my.sharepoint.com/personal/henriettam_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BA8B9F935-E5A1-47AD-B052-D5ED30E682AB%7D&file=XT1000%20Marke
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=b187e1dd-7687-49e0-87ff-6250e61e56ac&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BA8B9F93
iconUrl: '',
activity: {
title: 'Modified, December 15 2016',
actorName: 'Henrietta Mueller',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
}
];

public static getRecentDocument(): Promise<IDocument> {


return new Promise<IDocument>((resolve: (document: IDocument) => void, reject: (error: any) => void): void => {
window.setTimeout((): void => {
resolve(DocumentsService.documents[0]);
}, 300);
});
}

public static getRecentDocuments(startFrom: number = 0): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (documents: IDocument[]) => void, reject: (error: any) => void): void => {
window.setTimeout((): void => {
resolve(DocumentsService.documents.slice(startFrom, startFrom + 3));
}, 300);
});
}
}

The DocumentsService class is a sample service that loads information about recent documents. In this example, it uses a static data set, but you could easily change its implementation to
load its data from a SharePoint document library. At this stage, the DocumentsService class offers a centralized point for all web parts to access their data, but it doesn't store the previously
loaded data. You will implement that later in this tutorial.

Create a barrel for the service files


When referencing files in a project, you point to their relative path. Whenever that path changes, you have to update all references to the particular file. Such changes are very likely at the
beginning of the project when the different elements are being added and the final project structure is unclear. To avoid frequent changes to file references in a project, you can use barrels.
A barrel is a container that combines a number of exported objects. By using barrels, you can abstract away the exact location of files from other elements in the project that are using them.
In the ./src/services/documentsService folder, create a new file named index.ts, and paste the following code:

export { IDocument } from './IDocument';


export { IDocumentActivity } from './IDocumentActivity';
export { DocumentsService } from './DocumentsService';

With this barrel defined, other elements in the project can reference any of the exported types by using the relative path to the ./src/services/documentsService folder instead of the exact
path to the individual files. For example, the IDocument interface can be referenced like this:
import { IDocument } from '../services/documentsService';

instead of:

import { IDocument } from '../services/documentsService/IDocument.ts';

If at some point you decided that it's better to move the IDocument.ts file to a subfolder or merge a few files together, the only thing that you would change is the path in the barrel
definition (./src/services/documentsService/index.ts). All elements in the project could still use the exact same relative path to the documentsService folder to reference the IDocument
interface.

Update references to the moved files to use the barrel


Because you have moved the IDocument.ts and IDocumentActivity.ts files to another location, you have to update their references. Thanks to the barrel, this is the last time that you have
to do this.
Update references in the Recent Documents web part
1. In the code editor, open the ./src/webparts/recentDocuments/components/IRecentDocumentsProps.ts file, and change its code to:

import { IDocument } from '../../../services/documentsService';

export interface IRecentDocumentsProps {


documents: IDocument[];
}

2. Open the ./src/webparts/recentDocuments/components/RecentDocuments.tsx file, and change the import statement of the IDocument interface to:

import { IDocument } from '../../../services/documentsService';

3. Open the ./src/webparts/recentDocuments/RecentDocumentsWebPart.ts file, and change the import statement of the IDocument interface to:

import { IDocument } from '../../services/documentsService';

Update references in the Recent Document web part


1. In the code editor, open the ./src/webparts/recentDocument/components/IRecentDocumentProps.ts file, and change its code to:

import { IDocument } from '../../../services/documentsService';

export interface IRecentDocumentProps {


document: IDocument;
}

2. Open the ./src/webparts/recentDocument/components/RecentDocument.tsx file, and change the import statement of the IDocument interface to:

import { IDocument } from '../../../services/documentsService';

3. Open the ./src/webparts/recentDocument/RecentDocumentWebPart.ts file, and change the import statement of the IDocument interface to:

import { IDocument } from '../../services/documentsService';

4. Verify that your changes work as expected, by running the following command from a command prompt in your project folder:

gulp serve
Load web part data by using the data service
With the data service ready, the next step is to refactor both web parts to use the data service to load their data.
Load information about the recently modified documents
1. In the code editor, open the ./src/webparts/recentDocuments/RecentDocumentsWebPart.ts file. Expand the import statement referencing the IDocument interface to:

import { IDocument, DocumentsService } from '../../services/documentsService';

2. Update the render method by using the following code:

export default class RecentDocumentsWebPart extends BaseClientSideWebPart<IRecentDocumentsWebPartProps> {


// ...
public render(): void {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'documents');

DocumentsService.getRecentDocuments()
.then((documents: IDocument[]): void => {
const element: React.ReactElement<IRecentDocumentsProps> = React.createElement(
RecentDocuments,
{
documents: documents
}
);

this.context.statusRenderer.clearLoadingIndicator(this.domElement);
ReactDom.render(element, this.domElement);
});
}
// ...
}

Load information about the most recently modified document


1. In the code editor, open the ./src/webparts/recentDocument/RecentDocumentWebPart.ts file. Expand the import statement referencing the IDocument interface to:

import { IDocument, DocumentsService } from '../../services/documentsService';

2. Update the render method by using the following code:


export default class RecentDocumentWebPart extends BaseClientSideWebPart<IRecentDocumentWebPartProps> {
// ...
public render(): void {
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'document');

DocumentsService.getRecentDocument()
.then((document: IDocument): void => {
const element: React.ReactElement<IRecentDocumentProps> = React.createElement(
RecentDocument,
{
document: document
}
);

this.context.statusRenderer.clearLoadingIndicator(this.domElement);
ReactDom.render(element, this.domElement);
});
}
// ...
}

3. Confirm that both web parts are working correctly by running the following command from a command prompt in your project folder:

gulp serve

Share data between web parts


Now that both web parts use the data service to load their data, the next step is to extend the data service so that it loads the data only once and reuses it for both web parts.
1. In the code editor, open the ./src/services/documentsService/DocumentsService.ts file, and paste the following code:

import { IDocument } from './IDocument';

export class DocumentsService {


private static documents: IDocument[] = [
{
title: 'Proposal for Jacksonville Expansion Ad Campaign',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BCBF65183-0378-485B-AB67-791E0FC81D72%7D&file=Jacksonville%2
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BCBF6
iconUrl: '',
activity: {
title: 'Modified, January 25 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Customer Feedback for ZT1000',
url: 'https://contoso-my.sharepoint.com/personal/miriamg_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7B5449CE24-BFB7-442E-843D-E0C86CEB71CC%7D&file=Customer%20Fee
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=ca6fa69c-347d-4c07-886c-67105dc5a357&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7B5449
iconUrl: '',
activity: {
title: 'Modified, January 23 2017',
actorName: 'Miriam Graham',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Asia Q3 Marketing Overview',
url: 'https://contoso-my.sharepoint.com/personal/alexw_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BFD077A94-AB7D-45F9-A810-36229E518A94%7D&file=Asia%20Q3%20Mark
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=18231116-2bf0-474c-93ee-eb362681b293&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BFD07
iconUrl: '',
activity: {
title: 'Modified, January 23 2017',
actorName: 'Alex Wilber',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'Trey Research Business Development Plan',
url: 'https://contoso.sharepoint.com/sites/contoso/Resources/Document%20Center/_layouts/15/WopiFrame.aspx?sourcedoc=%7B743A6C44-D3F8-4ECC-A1B7-EA9844911314%7D&file=Trey%20Research%
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=923a6ce1-7b67-4bd0-a59f-89d37f233804&guidWeb=c12486eb-661c-46c7-baba-073a8a45b610&guidFile=%7B743A
iconUrl: '',
activity: {
title: 'Modified, January 15 2017',
actorName: 'Alex Wilber',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
},
{
title: 'XT1000 Marketing Analysis',
url: 'https://contoso-my.sharepoint.com/personal/henriettam_contoso_onmicrosoft_com/_layouts/15/WopiFrame.aspx?sourcedoc=%7BA8B9F935-E5A1-47AD-B052-D5ED30E682AB%7D&file=XT1000%20Ma
imageUrl: 'https://contoso-my.sharepoint.com/_layouts/15/getpreview.ashx?guidSite=b187e1dd-7687-49e0-87ff-6250e61e56ac&guidWeb=237a3f3f-59a4-46e8-b0a8-6effd78bd327&guidFile=%7BA8B9
iconUrl: '',
activity: {
title: 'Modified, December 15 2016',
actorName: 'Henrietta Mueller',
actorImageUrl: 'https://contoso-my.sharepoint.com/_vti_bin/DelveApi.ashx/people/[email protected]&size=L'
}
}
];

public static getRecentDocument(): Promise<IDocument> {


return new Promise<IDocument>((resolve: (document: IDocument) => void, reject: (error: any) => void): void => {
DocumentsService.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments[0]);
});
});
}

public static getRecentDocuments(startFrom: number = 0): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (documents: IDocument[]) => void, reject: (error: any) => void): void => {
DocumentsService.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments.slice(startFrom, startFrom + 3));
});
});
}

private static ensureRecentDocuments(): Promise<IDocument[]> {


return new Promise<IDocument[]>((resolve: (recentDocuments: IDocument[]) => void, reject: (error: any) => void): void => {
if ((window as any).loadedData) {
// data already loaded. reuse
resolve((window as any).loadedData);
return;
}

if ((window as any).loadingData) {
// data is being loaded. wait a moment and try again
window.setTimeout((): void => {
DocumentsService.ensureRecentDocuments()
.then((recentDocuments: IDocument[]): void => {
resolve(recentDocuments);
});
}, 100);
}
else {
(window as any).loadingData = true;
window.setTimeout((): void => {
// store the data for subsequent requests and resolve the Promise
(window as any).loadedData = DocumentsService.documents;
(window as any).loadingData = false;
resolve((window as any).loadedData);
}, 300);
}
});
}
}

The first time a web part calls the data service to load its data, the service sets the loadingData global variable to true . This indicates that data is currently being loaded. This is
required to prevent data from being loaded multiple times should, for instance, another web part request loading its data while the initial request to load data has not yet completed. In
this example, the data is loaded from a static data set, but you could easily change the implementation to load the data from a SharePoint document library.
After the data is loaded, it is stored in the loadedData global variable. The value of the loadingData variable is set to false and the promise is resolved with the retrieved data. The
next time a web part requests its data, the data service returns the data loaded previously, eliminating any additional requests to the remote APIs.
2. Confirm that both web parts are working correctly by running the following command from a command prompt in your project folder:

gulp serve

3. If you were to add logging statements in the different parts of the DocumentsService.ensureRecentDocuments method, you would see that the data is loaded once and reused for the
second web part.
See also
Share data between client-side web parts
Build custom controls for the property pane
5/3/2018 • 18 minutes to read Edit Online

The SharePoint Framework contains a set of standard controls for the property pane. But sometimes you need additional functionality beyond the basic controls. You might need
asynchronous updates to the data on a control or a specific user interface. Build a custom control for the property pane to get the functionality you need.
In this article, you will build a custom dropdown control that loads its data asynchronously from an external service without blocking the user interface of the web part.

The source of the working web part is available on GitHub at sp-dev-fx-webparts/samples/react-custompropertypanecontrols/.


NOTE

Before following the steps in this article, be sure to set up your development environment for building SharePoint Framework solutions.

Create new project


1. Start by creating a new folder for your project:

md react-custompropertypanecontrol

2. Go to the project folder:

cd react-custompropertypanecontrol

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, enter the following values:


react-custompropertypanecontrol as your solution name
Use the current folder for the location to place the files
List items as your web part name
Shows list items from the selected list as your web part description
React as the starting point to build the web part
5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. This article uses Visual Studio Code in the steps and screenshots, but you can use any editor that you prefer.

Define web part property for storing the selected list


The web part you are building shows list items from the selected SharePoint list. Users are able to select a list in the web part properties. To store the selected list, create a new web part
property named listName.
1. In the code editor, open the src/webparts/listItems/ListItemsWebPartManifest.json file. Replace the default description property with a new property named listName .
2. Open the src/webparts/listItems/IListItemsWebPartProps.ts file and replace its contents with:

export interface IListItemsWebPartProps {


listName: string;
}

3. In the src/webparts/listItems/ListItemsWebPart.ts file, change the render method to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IListItemsProps> = React.createElement(ListItems, {
listName: this.properties.listName
});

ReactDom.render(element, this.domElement);
}
// ...
}

4. Update the getPropertyPaneConfiguration method to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListFieldLabel
})
]
}
]
}
]
};
}
// ...
}

5. In the src/webparts/listItems/loc/mystrings.d.ts file, change the IListItemsWebPartStrings interface to:

declare interface IListItemsWebPartStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListFieldLabel: string;
}

6. In the src/webparts/listItems/loc/en-us.js file, add the missing definition for the ListFieldLabel string.
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListFieldLabel": "List"
}
});

7. In the src/webparts/listItems/components/ListItems.tsx file, change the contents of the render method to:

export default class ListItems extends React.Component<IListItemsProps, {}> {


public render(): React.ReactElement<IListItemsProps> {
return (
<div className={styles.listItems}>
<div className={styles.container}>
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className="ms-Grid-col ms-lg10 ms-xl8 ms-xlPush2 ms-lgPush1">
<span className="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
<p className="ms-font-l ms-fontColor-white">Customize SharePoint experiences using web parts.</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.listName)}</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

8. Open the src/webparts/listItems/components/IListItemsProps.ts file, and replace its contents with:

export interface IListItemsProps {


listName: string;
}

9. Run the following command to verify that the project is running:

gulp serve

10. In the web browser, add the List items web part to the canvas and open its properties. Verify that the value set for the List property is displayed in the web part body.

Create asynchronous dropdown property pane control


The SharePoint Framework offers you a standard dropdown control that allows users to select a specific value. The dropdown control is built in a way that requires all its values to be known
upfront. If you want to load the values dynamically or you're loading values asynchronously from an external service and you don't want to block the whole web part, building a custom
dropdown control is a viable option.
When creating a custom property pane control that uses React in the SharePoint Framework, the control consists of a class that registers the control with the web part, and a React
component that renders the dropdown and manages its data.
NOTE

In drop 6 of the SharePoint Framework, there is a bug in the Office UI Fabric React Dropdown component that causes the control built in this article to work incorrectly. A temporary
workaround is to edit the node_modules/@microsoft/office-ui-fabric-react-bundle/dist/office-ui-fabric-react.bundle.js file and change line 12027 from:
isDisabled: this.props.isDisabled !== undefined ? this.props.isDisabled : this.props.disabled

to:

isDisabled: newProps.isDisabled !== undefined ? newProps.isDisabled : newProps.disabled

Add asynchronous dropdown property pane control React component


1. Create the components folder. In the project src folder, create a hierarchy of three new folders so that your folder structure appears as
src/controls/PropertyPaneAsyncDropdown/components.

2. Define asynchronous dropdown React component properties. In the src/controls/PropertyPaneAsyncDropdown/components folder, create a new file named
IAsyncDropdownProps.ts, and enter the following code:

import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';

export interface IAsyncDropdownProps {


label: string;
loadOptions: () => Promise<IDropdownOption[]>;
onChanged: (option: IDropdownOption, index?: number) => void;
selectedKey: string | number;
disabled: boolean;
stateKey: string;
}

The IAsyncDropdownProps class defines properties that can be set on the React component used by the custom property pane control:
The label property specifies the label for the dropdown control.
The function associated with the loadOptions delegate is called by the control to load the available options.
The function associated with the onChanged delegate is called after the user selects an option in the dropdown.
The selectedKey property specifies the selected value, which can be a string or a number.
The disabled property specifies if the dropdown control is disabled or not.
The stateKey property is used to force the React component to re-render.
3. Define asynchronous dropdown React component interface. In the src/controls/PropertyPaneAsyncDropdown/components folder, create a new file named
IAsyncDropdownState.ts, and enter the following code:

import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';

export interface IAsyncDropdownState {


loading: boolean;
options: IDropdownOption[];
error: string;
}

The IAsyncDropdownState interface describes the state of the React component:


The loading property determines if the component is loading its options at the given moment.
The options property contains all available options.
If an error occurred, it is assigned to the error property from where it is communicated to the user.
4. Define the asynchronous dropdown React component. In the src/controls/PropertyPaneAsyncDropdown/components folder, create a new file named AsyncDropdown.tsx, and
enter the following code:
import * as React from 'react';
import { Dropdown, IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
import { Spinner } from 'office-ui-fabric-react/lib/components/Spinner';
import { IAsyncDropdownProps } from './IAsyncDropdownProps';
import { IAsyncDropdownState } from './IAsyncDropdownState';

export default class AsyncDropdown extends React.Component<IAsyncDropdownProps, IAsyncDropdownState> {


private selectedKey: React.ReactText;

constructor(props: IAsyncDropdownProps, state: IAsyncDropdownState) {


super(props);
this.selectedKey = props.selectedKey;

this.state = {
loading: false,
options: undefined,
error: undefined
};
}

public componentDidMount(): void {


this.loadOptions();
}

public componentDidUpdate(prevProps: IAsyncDropdownProps, prevState: IAsyncDropdownState): void {


if (this.props.disabled !== prevProps.disabled ||
this.props.stateKey !== prevProps.stateKey) {
this.loadOptions();
}
}

private loadOptions(): void {


this.setState({
loading: true,
error: undefined,
options: undefined
});

this.props.loadOptions()
.then((options: IDropdownOption[]): void => {
this.setState({
loading: false,
error: undefined,
options: options
});
}, (error: any): void => {
this.setState((prevState: IAsyncDropdownState, props: IAsyncDropdownProps): IAsyncDropdownState => {
prevState.loading = false;
prevState.error = error;
return prevState;
});
});
}

public render(): JSX.Element {


const loading: JSX.Element = this.state.loading ? <div><Spinner label={'Loading options...'} /></div> : <div />;
const error: JSX.Element = this.state.error !== undefined ? <div className={'ms-TextField-errorMessage ms-u-slideDownIn20'}>Error while loading items: {this.state.error}</div> : <div />;

return (
<div>
<Dropdown label={this.props.label}
disabled={this.props.disabled || this.state.loading || this.state.error !== undefined}
onChanged={this.onChanged.bind(this)}
selectedKey={this.selectedKey}
options={this.state.options} />
{loading}
{error}
</div>
);
}

private onChanged(option: IDropdownOption, index?: number): void {


this.selectedKey = option.key;
// reset previously selected options
const options: IDropdownOption[] = this.state.options;
options.forEach((o: IDropdownOption): void => {
if (o.key !== option.key) {
o.selected = false;
}
});
this.setState((prevState: IAsyncDropdownState, props: IAsyncDropdownProps): IAsyncDropdownState => {
prevState.options = options;
return prevState;
});
if (this.props.onChanged) {
this.props.onChanged(option, index);
}
}
}

The AsyncDropdown class represents the React component used to render the asynchronous dropdown property pane control:
When the component first loads, the componentDidMount method, or its disabled or stateKey properties, change, and it loads the available options by calling the
loadOptions method passed through the properties.
After the options are loaded, the component updates its state showing the available options.
The dropdown itself is rendered by using the Office UI Fabric React dropdown component.
When the component is loading the available options, it displays a spinner by using the Office UI Fabric React spinner component.
The next step is to define the custom property pane control. This control is used inside the web part when defining properties in the property pane, and renders using the previously defined
React component.
Add asynchronous dropdown property pane control
1. Define asynchronous dropdown property pane control properties. A custom property pane control has two sets of properties.
The first set of properties are exposed publicly and are used to define the web part property inside the web part. These properties are component-specific properties, such as the label
displayed next to the control, minimum and maximum values for a spinner, or available options for a dropdown. When defining a custom property pane control, the type describing
these properties must be passed as the TProperties type when implementing the IPropertyPaneField<TProperties> interface.
The second set of properties are private properties used internally inside the custom property pane control. These properties have to adhere to the SharePoint Framework APIs for
the custom control to render correctly. These properties must implement the IPropertyPaneCustomFieldProps interface from the @microsoft/sp-webpart-base package.
2. Define the public properties for the asynchronous dropdown property pane control. In the src/controls/PropertyPaneAsyncDropdown folder, create a new file named
IPropertyPaneAsyncDropdownProps.ts, and enter the following code:

import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';

export interface IPropertyPaneAsyncDropdownProps {


label: string;
loadOptions: () => Promise<IDropdownOption[]>;
onPropertyChange: (propertyPath: string, newValue: any) => void;
selectedKey: string | number;
disabled?: boolean;
}

In the IPropertyPaneAsyncDropdownProps interface:


The label property defines the label displayed next to the dropdown.
The loadOptions delegate defines the method that is called to load the available dropdown options.
The onPropertyChange delegate defines a method that is called when the user selects a value in the dropdown.
The selectedKey property returns the selected dropdown value.
The disabled property specifies whether the control is disabled or not.
3. Define the internal properties for the asynchronous dropdown property pane control. In the src/controls/PropertyPaneAsyncDropdown folder, create a new file named
IPropertyPaneAsyncDropdownInternalProps.ts, and enter the following code:

import { IPropertyPaneCustomFieldProps } from '@microsoft/sp-webpart-base';


import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';

export interface IPropertyPaneAsyncDropdownInternalProps extends IPropertyPaneAsyncDropdownProps, IPropertyPaneCustomFieldProps {


}

While the IPropertyPaneAsyncDropdownInternalProps interface doesn't define any new properties, it combines the properties from the previously defined
IPropertyPaneAsyncDropdownProps interface and the standard SharePoint Framework IPropertyPaneCustomFieldProps interface, which is required for a custom control to
run correctly.
4. Define the asynchronous dropdown property pane control. In the src/controls/PropertyPaneAsyncDropdown folder, create a new file named PropertyPaneAsyncDropdown.ts,
and enter the following code:
import * as React from 'react';
import * as ReactDom from 'react-dom';
import {
IPropertyPaneField,
PropertyPaneFieldType
} from '@microsoft/sp-webpart-base';
import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';
import { IPropertyPaneAsyncDropdownProps } from './IPropertyPaneAsyncDropdownProps';
import { IPropertyPaneAsyncDropdownInternalProps } from './IPropertyPaneAsyncDropdownInternalProps';
import AsyncDropdown from './components/AsyncDropdown';
import { IAsyncDropdownProps } from './components/IAsyncDropdownProps';

export class PropertyPaneAsyncDropdown implements IPropertyPaneField<IPropertyPaneAsyncDropdownProps> {


public type: PropertyPaneFieldType = PropertyPaneFieldType.Custom;
public targetProperty: string;
public properties: IPropertyPaneAsyncDropdownInternalProps;
private elem: HTMLElement;

constructor(targetProperty: string, properties: IPropertyPaneAsyncDropdownProps) {


this.targetProperty = targetProperty;
this.properties = {
key: properties.label,
label: properties.label,
loadOptions: properties.loadOptions,
onPropertyChange: properties.onPropertyChange,
selectedKey: properties.selectedKey,
disabled: properties.disabled,
onRender: this.onRender.bind(this)
};
}

public render(): void {


if (!this.elem) {
return;
}

this.onRender(this.elem);
}

private onRender(elem: HTMLElement): void {


if (!this.elem) {
this.elem = elem;
}

const element: React.ReactElement<IAsyncDropdownProps> = React.createElement(AsyncDropdown, {


label: this.properties.label,
loadOptions: this.properties.loadOptions,
onChanged: this.onChanged.bind(this),
selectedKey: this.properties.selectedKey,
disabled: this.properties.disabled,
// required to allow the component to be re-rendered by calling this.render() externally
stateKey: new Date().toString()
});
ReactDom.render(element, elem);
}

private onChanged(option: IDropdownOption, index?: number): void {


this.properties.onPropertyChange(this.targetProperty, option.key);
}
}

The PropertyPaneAsyncDropdown class implements the standard SharePoint Framework IPropertyPaneField interface by using the IPropertyPaneAsyncDropdownProps
interface as a contract for its public properties that can be set from inside the web part. The class contains the following three public properties defined by the IPropertyPaneField
interface:
type: Must be set to PropertyPaneFieldType.Custom for a custom property pane control.
targetProperty: Used to specify the name of the web part property to be used with the control.
properties: Used to define control-specific properties.
Notice how the properties property is of the internal IPropertyPaneAsyncDropdownInternalProps type rather than the public IPropertyPaneAsyncDropdownProps interface
implemented by the class. This is on purpose so that the properties property can define the onRender method required by the SharePoint Framework. If the onRender method was
a part of the public IPropertyPaneAsyncDropdownProps interface, when using the asynchronous dropdown control in the web part, you would be required to assign a value to it
inside the web part, which isn't desirable.
The PropertyPaneAsyncDropdown class defines a public render method, which can be used to repaint the control. This is useful in situations such as when you have cascading
dropdowns where the value set in one determines the options available in another. By calling the render method after selecting an item, you can have the dependent dropdown load
available options. For this to work, you have to make React detect that the control has changed. This is done by setting the value of the stateKey to the current date. Using this trick,
every time the onRender method is called, the component not only is re-rendered but also updates its available options.

Use the asynchronous dropdown property pane control in the web part
With the asynchronous dropdown property pane control ready, the next step is to use it inside the web part, allowing users to select a list.

Add list info interface


To pass information about available lists around in a consistent manner, define an interface that represents information about a list. In the src/webparts/listItems folder, create a new file
named IListInfo.ts, and enter the following code:

export interface IListInfo {


Id: string;
Title: string;
}

Use the asynchronous dropdown property pane control to render the listName web part property
1. Reference required types. In the top section of the src/webparts/listItems/ListItemsWebPart.ts file, import the previously created PropertyPaneAsyncDropdown class by
adding:
import { PropertyPaneAsyncDropdown } from '../../controls/PropertyPaneAsyncDropdown/PropertyPaneAsyncDropdown';

2. After that code, add a reference to the IDropdownOption interface and two helpers functions required to work with web part properties.

import { IDropdownOption } from 'office-ui-fabric-react/lib/components/Dropdown';


import { update, get } from '@microsoft/sp-lodash-subset';

3. Add method to load available lists. In the ListItemsWebPart class, add a method to load available lists. In this article you use mock data, but you could also call the SharePoint REST
API to retrieve the list of available lists from the current web. To simulate loading options from an external service, the method uses a two second delay.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private loadLists(): Promise<IDropdownOption[]> {
return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
setTimeout(() => {
resolve([{
key: 'sharedDocuments',
text: 'Shared Documents'
},
{
key: 'myDocuments',
text: 'My Documents'
}]);
}, 2000);
});
}
}

4. Add method to handle the change of the value in the dropdown. In the ListItemsWebPart class, add a new method named onListChange.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private onListChange(propertyPath: string, newValue: any): void {
const oldValue: any = get(this.properties, propertyPath);
// store new value in web part properties
update(this.properties, propertyPath, (): any => { return newValue; });
// refresh web part
this.render();
}
}

After selecting a list in the list dropdown, the selected value should be persisted in web part properties, and the web part should be re-rendered to reflect the selected property.
5. Render the list web part property by using the asynchronous dropdown property pane control. In the ListItemsWebPart class, change the getPropertyPaneConfiguration method
to use the asynchronous dropdown property pane control to render the listName web part property.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
new PropertyPaneAsyncDropdown('listName', {
label: strings.ListFieldLabel,
loadOptions: this.loadLists.bind(this),
onPropertyChange: this.onListChange.bind(this),
selectedKey: this.properties.listName
})
]
}
]
}
]
};
}
// ...
}

6. At this point, you should be able to select a list by using the newly created asynchronous dropdown property pane control. To verify that the control is working as expected, open the
command-line and run:

gulp serve
Implement cascading dropdowns using the asynchronous dropdown property pane control
When building SharePoint Framework web parts, you might need to implement a configuration where the available options depend on another option chosen previously. A common
example is to first let users choose a list and from that list select a list item. The list of available items would depend on the selected list. Here is how to implement such a scenario by using
the asynchronous dropdown property pane control implemented in previous steps.

Add item web part property


1. In the code editor, open the src/webparts/listItems/ListItemsWebPart.manifest.json file. To the properties section, add a new property named item so that it appears as follows:

// ...
"properties": {
"listName": "",
"item": ""
}
// ...

2. Change the code in the src/webparts/listItems/IListItemsWebPartProps.ts file to:

export interface IListItemsWebPartProps {


listName: string;
item: string;
}

3. Change the contents of the src/webparts/listItems/components/IListItemsProps.ts file to:


export interface IListItemsProps {
listName: string;
item: string;
}

4. In the src/webparts/listItems/ListItemsWebPart.ts file, change the code of the render method to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IListItemsProps> = React.createElement(ListItems, {
listName: this.properties.listName,
item: this.properties.item
});

ReactDom.render(element, this.domElement);
}
// ...
}

5. In the src/webparts/listItems/loc/mystrings.d.ts file, change the IListItemsWebPartStrings interface to:

declare interface IListItemsWebPartStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListFieldLabel: string;
ItemFieldLabel: string;
}

6. In the src/webparts/listItems/loc/en-us.js file, add the missing definition for the ItemFieldLabel string:

define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListFieldLabel": "List",
"ItemFieldLabel": "Item"
}
});

Render the value of the item web part property


In the src/webparts/listItems/components/ListItems.tsx file, change the render method to:

export default class ListItems extends React.Component<IListItemsProps, {}> {


public render(): React.ReactElement<IListItemsProps> {
return (
<div className={styles.listItems}>
<div className={styles.container}>
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className="ms-Grid-col ms-lg10 ms-xl8 ms-xlPush2 ms-lgPush1">
<span className="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
<p className="ms-font-l ms-fontColor-white">Customize SharePoint experiences using web parts.</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.listName)}</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.item)}</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

Add method to load list items


In the src/webparts/listItems/ListItemsWebPart.ts file, in the ListItemsWebPart class, add a new method to load available list items from the selected list. Like the method for loading
available lists, you use mock data. Depending on the previously selected list, the loadItems method returns mock list items. When no list has been selected, the method resolves the promise
without any data.
export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
// ...
private loadItems(): Promise<IDropdownOption[]> {
if (!this.properties.listName) {
// resolve to empty options since no list has been selected
return Promise.resolve();
}

const wp: ListItemsWebPart = this;

return new Promise<IDropdownOption[]>((resolve: (options: IDropdownOption[]) => void, reject: (error: any) => void) => {
setTimeout(() => {
const items = {
sharedDocuments: [
{
key: 'spfx_presentation.pptx',
text: 'SPFx for the masses'
},
{
key: 'hello-world.spapp',
text: 'hello-world.spapp'
}
],
myDocuments: [
{
key: 'isaiah_cv.docx',
text: 'Isaiah CV'
},
{
key: 'isaiah_expenses.xlsx',
text: 'Isaiah Expenses'
}
]
};
resolve(items[wp.properties.listName]);
}, 2000);
});
}
}

Add method to handle the selection of an item


In the ListItemsWebPart class, add a new method named onListItemChange. After selecting an item in the items dropdown, the web part should store the new value in web part
properties and re-render the web part to reflect the changes in the user interface.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private onListItemChange(propertyPath: string, newValue: any): void {
const oldValue: any = get(this.properties, propertyPath);
// store new value in web part properties
update(this.properties, propertyPath, (): any => { return newValue; });
// refresh web part
this.render();
}
}

Render the item web part property in the property pane


1. In the ListItemsWebPart class, add a new class property named itemsDropdown:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


private itemsDropDown: PropertyPaneAsyncDropdown;
// ...
}

2. Change the code of the getPropertyPaneConfiguration method to:


export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
// reference to item dropdown needed later after selecting a list
this.itemsDropDown = new PropertyPaneAsyncDropdown('item', {
label: strings.ItemFieldLabel,
loadOptions: this.loadItems.bind(this),
onPropertyChange: this.onListItemChange.bind(this),
selectedKey: this.properties.item,
// should be disabled if no list has been selected
disabled: !this.properties.listName
});

return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
new PropertyPaneAsyncDropdown('listName', {
label: strings.ListFieldLabel,
loadOptions: this.loadLists.bind(this),
onPropertyChange: this.onListChange.bind(this),
selectedKey: this.properties.listName
}),
this.itemsDropDown
]
}
]
}
]
};
}
// ...
}

The dropdown for the item property is initialized similarly to the dropdown for the listName property. The only difference is because after selecting a list, the items dropdown has to
be refreshed, an instance of the control has to be assigned to the class variable.

Load items for the selected list


Initially when no list is selected, the items dropdown is disabled and becomes enabled after the user selects a list. After selecting a list, the items dropdown also loads list items from that list.
1. To implement this logic, extend the previously defined onListChange method to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private onListChange(propertyPath: string, newValue: any): void {
const oldValue: any = get(this.properties, propertyPath);
// store new value in web part properties
update(this.properties, propertyPath, (): any => { return newValue; });
// reset selected item
this.properties.item = undefined;
// store new value in web part properties
update(this.properties, 'item', (): any => { return this.properties.item; });
// refresh web part
this.render();
// reset selected values in item dropdown
this.itemsDropDown.properties.selectedKey = this.properties.item;
// allow to load items
this.itemsDropDown.properties.disabled = false;
// load items and re-render items dropdown
this.itemsDropDown.render();
}
// ...
}

After selecting a list, the selected item is reset, persisted in web part properties, and reset in the items dropdown. The dropdown for selecting an item becomes enabled, and the
dropdown is refreshed in order to load its options.
2. To verify that everything is working as expected, in the command-line run:

gulp serve

After adding the web part to the page for the first time and opening its property pane, you should see both dropdowns disabled and loading their options.
After the options have been loaded, the list dropdown becomes enabled. Because no list has been selected yet, the item dropdown remains disabled.

After selecting a list in the list dropdown the item dropdown will load items available in that list.
After the available items have been loaded, the item dropdown becomes enabled.

After selecting an item in the item dropdown the web part is refreshed showing the selected item in its body.
See also
Use cascading dropdowns in web part properties
Use cascading dropdowns in web part properties
5/3/2018 • 13 minutes to read Edit Online

When designing the property pane for your SharePoint client-side web parts, you may have one web part property that displays its options based on the value selected in another property.
This scenario typically occurs when implementing cascading dropdown controls. In this article, you learn how to create cascading dropdown controls in the web part property pane without
developing a custom property pane control.

The source of the working web part is available on GitHub at sp-dev-fx-webparts/samples/react-custompropertypanecontrols/.


NOTE

Before following the steps in this article, be sure to set up your SharePoint client-side web part development environment.

Create new project


1. Start by creating a new folder for your project:

md react-cascadingdropdowns

2. Go to the project folder:

cd react-cascadingdropdowns

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, enter the following values:


react-cascadingdropdowns as your solution name
Use the current folder for the location to place the files
React as the starting point to build the web part
List items as your web part name
Shows list items from the selected list as your web part description
5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. This article uses Visual Studio Code in the steps and screenshots, but you can use any editor that you prefer.

Define a web part property to store the selected list


You will build a web part that displays list items from a selected SharePoint list. Users are able to select a list in the web part property pane. To store the selected list, create a new web part
property named listName.
1. In the code editor, open the src/webparts/listItems/ListItemsWebPartManifest.json file. Replace the default description property with a new property named listName .
2. Open the src/webparts/listItems/IListItemsWebPartProps.ts file, and replace its contents with:

export interface IListItemsWebPartProps {


listName: string;
}

3. In the src/webparts/listItems/ListItemsWebPart.ts file, change the render method to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IListItemsProps> = React.createElement(ListItems, {
listName: this.properties.listName
});

ReactDom.render(element, this.domElement);
}
// ...
}

4. Update propertyPaneSettings to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}
// ...
}

5. In the src/webparts/listItems/loc/mystrings.d.ts file, change the IListItemsStrings interface to:

declare interface IListItemsStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
}

6. In the src/webparts/listItems/loc/en-us.js file, add the missing definition for the ListNameFieldLabel string:
define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListNameFieldLabel": "List"
}
});

7. In the src/webparts/listItems/components/ListItems.tsx file, change the contents of the render method to:

export default class ListItems extends React.Component<IListItemsProps, {}> {


public render(): JSX.Element {
return (
<div className={styles.listItems}>
<div className={styles.container}>
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<span className="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
<p className="ms-font-l ms-fontColor-white">Customize SharePoint experiences using web parts.</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.listName)}</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

8. In the src/webparts/listItems/components/IListItemsProps.ts file, change the IListItemsProps interface to:

export interface IListItemsProps {


listName: string;
}

9. Run the following command to verify that the project is running:

gulp serve

10. In the web browser, add the List items web part to the canvas and open its properties. Verify that the value set for the List property is displayed in the web part body.

Populate the dropdown with SharePoint lists to choose from


At this point, a user specifies which list the web part should use by manually entering the list name. This is error-prone, and ideally you want users to choose one of the lists existing in the
current SharePoint site.

Use dropdown control to render the listName property


1. In the ListItemsWebPart class, add a reference to the PropertyPaneDropdown class in the top section of the web part. Replace the import clause that loads the
PropertyPaneTextField class with:

import {
BaseClientSideWebPart,
IPropertyPaneConfiguration,
PropertyPaneTextField,
PropertyPaneDropdown,
IPropertyPaneDropdownOption
} from '@microsoft/sp-webpart-base';

2. In the ListItemsWebPart class, add a new variable named lists to store information about all available lists in the current site:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


private lists: IPropertyPaneDropdownOption[];
// ...
}
3. Add a new class variable named listsDropdownDisabled. This variable determines whether the list dropdown is enabled or not. Until the web part retrieves the information about
the lists available in the current site, the dropdown should be disabled.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private listsDropdownDisabled: boolean = true;
// ...
}

4. Change the propertyPaneSettings getter to use the dropdown control to render the listName property:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
protected get propertyPaneSettings(): IPropertyPaneSettings {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('listName', {
label: strings.ListNameFieldLabel,
options: this.lists,
disabled: this.listsDropdownDisabled
})
]
}
]
}
]
};
}
}

5. Run the following command to verify that it's working as expected:

gulp serve

Show available lists in the list dropdown


Previously, you associated the dropdown control of the listName property with the lists class property. Because you haven't loaded any values into it yet, the List dropdown in the web part
property pane remains disabled. In this section, you will extend the web part to load the information about available lists.
1. In the ListItemsWebPart class, add a method to load available lists. You will use mock data, but you could also call the SharePoint REST API to retrieve the list of available lists from
the current web. To simulate loading options from an external service, the method uses a two-second delay.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private loadLists(): Promise<IPropertyPaneDropdownOption[]> {
return new Promise<IPropertyPaneDropdownOption[]>((resolve: (options: IPropertyPaneDropdownOption[]) => void, reject: (error: any) => void) => {
setTimeout((): void => {
resolve([{
key: 'sharedDocuments',
text: 'Shared Documents'
},
{
key: 'myDocuments',
text: 'My Documents'
}]);
}, 2000);
});
}
}

2. Load information about available lists into the list dropdown. In the ListItemsWebPart class, override the onPropertyPaneConfigurationStart method by using the following code:
export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
// ...
protected onPropertyPaneConfigurationStart(): void {
this.listsDropdownDisabled = !this.lists;

if (this.lists) {
return;
}

this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'lists');

this.loadLists()
.then((listOptions: IPropertyPaneDropdownOption[]): void => {
this.lists = listOptions;
this.listsDropdownDisabled = false;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
}
// ...
}

The onPropertyPaneConfigurationStart method is called by the SharePoint Framework after the web part property pane for the web part has been opened.
First, the method checks if the information about the lists available in the current site has been loaded.
If the list information is loaded, the list dropdown is enabled.
If the list information about lists has not been loaded yet, the loading indicator is displayed, which informs the user that the web part is loading information about lists.

After the information about available lists has been loaded, the method assigns the retrieved data to the lists class variable, from which it can be used by the list dropdown.
Next, the dropdown is enabled allowing the user to select a list. By calling this.context.propertyPane.refresh(), the web part property pane is refreshed and it reflects the latest
changes to the list dropdown.
After list information is loaded, the loading indicator is removed by a call to the clearLoadingIndicator method. Because calling this method clears the web part user interface, the
render method is called to force the web part to re-render.
3. Run the following command to confirm that everything is working as expected:

gulp serve

When you add a web part to the canvas and open its property pane, you should see the lists dropdown filled with available lists for the user to choose from.
Allow users to select an item from the selected list
When building web parts, you often need to allow users to choose an option from a set of values determined by a previously selected value, such as choosing a country/region based on the
selected continent, or choosing a list item from a selected list. This user experience is often referred to as cascading dropdowns. Using the standard SharePoint Framework client-side web
parts capabilities, you can build cascading dropdowns in the web part property pane. To do this, you extend the previously built web part with the ability to choose a list item based on the
previously selected list.

Add item web part property


1. In the code editor, open the src/webparts/listItems/ListItemsWebPart.manifest.json file. To the properties section, add a new property named itemName so that it appears as
follows:

{
// ...
"properties": {
"listName": "",
"itemName": ""
}
// ...
}
2. Change the code in the src/webparts/listItems/IListItemsWebPartProps.ts file to:

export interface IListItemsWebPartProps {


listName: string;
itemName: string;
}

3. Change the code in the src/webparts/listItems/components/IListItemsProps.ts file to:

export interface IListItemsProps {


listName: string;
itemName: string;
}

4. In the src/webparts/listItems/ListItemsWebPart.ts file, change the code of the render method to:

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
public render(): void {
const element: React.ReactElement<IListItemsProps> = React.createElement(ListItems, {
listName: this.properties.listName,
itemName: this.properties.itemName
});

ReactDom.render(element, this.domElement);
}
// ...
}

5. In the src/webparts/listItems/loc/mystrings.d.ts file, change the IListItemsStrings interface to:

declare interface IListItemsStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
ItemNameFieldLabel: string;
}

6. In the src/webparts/listItems/loc/en-us.js file, add the missing definition for the ItemNameFieldLabel string.

define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListNameFieldLabel": "List",
"ItemNameFieldLabel": "Item"
}
});

Render the value of the item web part property


In the src/webparts/listItems/components/ListItems.tsx file, change the render method to:
export default class ListItems extends React.Component<IListItemsProps, {}> {
public render(): JSX.Element {
return (
<div className={styles.listItems}>
<div className={styles.container}>
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<span className="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
<p className="ms-font-l ms-fontColor-white">Customize SharePoint experiences using web parts.</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.listName)}</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.itemName)}</p>
<a href="https://aka.ms/spfx" className={styles.button}>
<span className={styles.label}>Learn more</span>
</a>
</div>
</div>
</div>
</div>
);
}
}

Allow users to choose the item from a list


Similar to how users can select a list by using a dropdown, they should be able to select the item from the list of available items.
1. In the ListItemsWebPart class, add a new variable named items, which you use to store information about all available items in the currently selected list.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private items: IPropertyPaneDropdownOption[];
// ...
}

2. Add a new class variable named itemsDropdownDisabled. This variable determines whether the items dropdown should be enabled or not. Users should be able to select an item
only after they selected a list.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private itemsDropdownDisabled: boolean = true;
// ...
}

3. Change the propertyPaneSettings getter to use the dropdown control to render the itemName property.

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneDropdown('listName', {
label: strings.ListNameFieldLabel,
options: this.lists,
disabled: this.listsDropdownDisabled
}),
PropertyPaneDropdown('itemName', {
label: strings.ItemNameFieldLabel,
options: this.items,
disabled: this.itemsDropdownDisabled
})
]
}
]
}
]
};
}
}

4. Run the following command to verify that it's working as expected:

gulp serve
Show items available in the selected list in the item dropdown
Previously, you defined a dropdown control to render the itemName property in the web part property pane. Next, you extend the web part to load the information about items available in
the selected list, and show the items in the item dropdown.
1. Add method to load list items. In the src/webparts/listItems/ListItemsWebPart.ts file, in the ListItemsWebPart class, add a new method to load available list items from the
selected list. (Like the method for loading available lists, you use mock data.)

export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {


// ...
private loadItems(): Promise<IPropertyPaneDropdownOption[]> {
if (!this.properties.listName) {
// resolve to empty options since no list has been selected
return Promise.resolve();
}

const wp: ListItemsWebPart = this;

return new Promise<IPropertyPaneDropdownOption[]>((resolve: (options: IPropertyPaneDropdownOption[]) => void, reject: (error: any) => void) => {
setTimeout(() => {
const items = {
sharedDocuments: [
{
key: 'spfx_presentation.pptx',
text: 'SPFx for the masses'
},
{
key: 'hello-world.spapp',
text: 'hello-world.spapp'
}
],
myDocuments: [
{
key: 'isaiah_cv.docx',
text: 'Isaiah CV'
},
{
key: 'isaiah_expenses.xlsx',
text: 'Isaiah Expenses'
}
]
};
resolve(items[wp.properties.listName]);
}, 2000);
});
}
}

The loadItems method returns mock list items for the previously selected list. When no list has been selected, the method resolves the promise without any data.
2. Load information about available items into the item dropdown. In the ListItemsWebPart class, extend the onPropertyPaneConfigurationStart method to load items for the
selected list:
export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
// ...
protected onPropertyPaneConfigurationStart(): void {
this.listsDropdownDisabled = !this.lists;
this.itemsDropdownDisabled = !this.properties.listName || !this.items;

if (this.lists) {
return;
}

this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'options');

this.loadLists()
.then((listOptions: IPropertyPaneDropdownOption[]): Promise<IPropertyPaneDropdownOption[]> => {
this.lists = listOptions;
this.listsDropdownDisabled = false;
this.context.propertyPane.refresh();
return this.loadItems();
})
.then((itemOptions: IPropertyPaneDropdownOption[]): void => {
this.items = itemOptions;
this.itemsDropdownDisabled = !this.properties.listName;
this.context.propertyPane.refresh();
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
this.render();
});
}
// ...
}

When initializing, the web part first determines if the items dropdown should be enabled or not. If the user previously selected a list, they can select an item from that list. If no list was
selected, the item dropdown is disabled.
You extended the previously defined code, which loads the information about available lists, to load the information about items available in the selected list. The code then assigns the
retrieved information to the items class variable for use by the item dropdown. Finally, the code clears the loading indicator and allows the user to start working with the web part.
3. Run the following command to confirm that everything is working as expected:

gulp serve

As required, initially the item dropdown is disabled, requiring users to select a list first. But at this point, even after a list has been selected, the item dropdown remains disabled.

4. Update web part property pane after selecting a list. When a user selects a list in the property pane, the web part should update, enabling the item dropdown and showing the list of
items available in the selected list.
In the ListItemsWebPart.ts file, in the ListItemsWebPart class, override the onPropertyPaneFieldChanged method with the following code:
export default class ListItemsWebPart extends BaseClientSideWebPart<IListItemsWebPartProps> {
// ...
protected onPropertyPaneFieldChanged(propertyPath: string, oldValue: any, newValue: any): void {
if (propertyPath === 'listName' &&
newValue) {
// push new list value
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
// get previously selected item
const previousItem: string = this.properties.itemName;
// reset selected item
this.properties.itemName = undefined;
// push new item value
this.onPropertyPaneFieldChanged('itemName', previousItem, this.properties.itemName);
// disable item selector until new items are loaded
this.itemsDropdownDisabled = true;
// refresh the item selector control by repainting the property pane
this.context.propertyPane.refresh();
// communicate loading items
this.context.statusRenderer.displayLoadingIndicator(this.domElement, 'items');

this.loadItems()
.then((itemOptions: IPropertyPaneDropdownOption[]): void => {
// store items
this.items = itemOptions;
// enable item selector
this.itemsDropdownDisabled = false;
// clear status indicator
this.context.statusRenderer.clearLoadingIndicator(this.domElement);
// re-render the web part as clearing the loading indicator removes the web part body
this.render();
// refresh the item selector control by repainting the property pane
this.context.propertyPane.refresh();
});
}
else {
super.onPropertyPaneFieldChanged(propertyPath, oldValue, newValue);
}
}
// ...
}

After the user selected a list, the web part persists the newly selected value. Because the selected list changed, the web part resets the previously selected list item. Now that a list is
selected, the web part property pane loads list items for that particular list. While loading items, the user shouldn't be able to select an item.
After the items for the selected list are loaded, they are assigned to the items class variable from where they can be referenced by the item dropdown. Now that the information about
available list items is available, the item dropdown is enabled allowing users to choose an item. The loading indicator is removed, which clears the web part body which is why the web
part should re-render. Finally, the web part property pane refreshes to reflect the latest changes.
NOTE

In drop 6 of the SharePoint Framework there is a bug in the Office UI Fabric React Dropdown component that causes the dropdown control to work incorrectly. A temporary
workaround is to edit the node_modules/@microsoft/office-ui-fabric-react-bundle/dist/office-ui-fabric-react.bundle.js file and change line 12027 from:

isDisabled: this.props.isDisabled !== undefined ? this.props.isDisabled : this.props.disabled

to:

isDisabled: newProps.isDisabled !== undefined ? newProps.isDisabled : newProps.disabled

See also
Build custom controls for the property pane
Migrate existing Script Editor web part customizations to the SharePoint Framework
5/3/2018 • 17 minutes to read Edit Online

SharePoint Framework is a model for building SharePoint customizations. If you have been building client-side SharePoint solutions by using the Script Editor web part, you might be
wondering what the possible advantages are of migrating them to the SharePoint Framework.
This article highlights the benefits of migrating existing client-side customizations to the SharePoint Framework and points out a number of considerations that you should take into account
when planning the migration.
NOTE

The information in this article applies to customizations built using both the Content- and the Script Editor web part. Wherever there is a reference to the Script Editor web part, you should
read Content Editor web part and Script Editor Web Part.

Benefits of migrating existing client-side customizations to the SharePoint Framework


SharePoint Framework has been built from the ground up as a SharePoint development model focusing on client-side development. While it primarily offers extensibility capabilities for
modern team sites, customizations built on the SharePoint Framework also work with the classic SharePoint experience. Building your customizations by using the SharePoint Framework
offers you a number of benefits over using any other SharePoint development model available to date.

Build once, reuse across all devices


The modern SharePoint user experience has been designed to natively support access to information stored in SharePoint on any device. Additionally, the modern experience supports the
SharePoint mobile app. Solutions built using the SharePoint Framework seamlessly integrate with the modern SharePoint experience and can be used across the different devices as well as
inside the SharePoint mobile app. Because SharePoint Framework solutions also work with the classic SharePoint experience, they can be used by organizations that haven't migrated to the
modern experience yet.

More robust and future-proof


In the past, developers were customizing SharePoint by attaching to specific DOM elements in the user interface. Using this approach, they could change the standard user experience or
provide additional functionality to end users. Such solutions were however error-prone. Because the SharePoint page DOM was never meant as an extensibility surface, whenever it changed,
solutions relying on it would break.
SharePoint Framework offers developers standardized API and extensibility points to build client-side solutions and provide end-users with additional capabilities. Developers can use the
SharePoint Framework to build solutions in a supported and future-proof way, and don't need to be concerned with how future changes to the SharePoint user interface could affect your
solution.

Easier access to information in SharePoint and Office 365


Microsoft is continuously extending capabilities of SharePoint and Office 365. As organizations make more use of both platforms, it's becoming increasingly important for developers to be
able to tap into the information and insights stored in Office 365 to build rich solutions. One of the goals of the SharePoint Framework is to make it easy for developers to connect to various
SharePoint and Office 365 APIs.

Enable users to configure solutions to their needs


Client-side solutions built through script embedding often didn't offer end-users an easy way to configure them to their needs. The only way to configure such solution, was by altering its
code or through a custom user interface built specifically for that purpose.
SharePoint Framework client-side web parts offer a standard way for configuring web parts using a property pane - familiar to users who worked with classic web parts in the past.
Developers building client-side web parts can choose whether their web part should have a reactive property pane (default), where each change to a web part property is directly reflected in
the web part body, or a non-reactive property pane, where changes to web part properties must be explicitly applied.

Work on no-script sites


To help organizations govern their customizations, Microsoft released the no-script capability in SharePoint Online. When the tenant or a particular site collection has the no-script flag
enabled, customizations relying on script injection and embedding are disabled.
Because SharePoint Framework client-side web parts are deployed through the app catalog with a prior approval, they are allowed to run in no-script sites. By default, modern team sites
have the no-script setting enabled and it's not possible to embed scripts in these sites. This makes using the SharePoint Framework the only supported way to build client-side
customizations in modern team sites.

Similarities between SharePoint Framework solutions and Script Editor web part customizations
While built on a new development model using a new toolchain, SharePoint Framework solutions are similar to the Script Editor web part customizations you have built in the past. Because
they share the same concepts, it makes it easier for you to migrate them to the new SharePoint Framework.

Run as a part of the page


Similar to customizations embedded in Script Editor web parts, SharePoint Framework solutions are part of the page. This gives these solutions access to the page's DOM and allows them
to communicate with other components on the same page. Also, it allows developers to more easily make their solutions responsive to adapt to the different form factors on which a
SharePoint page could be displayed, including the SharePoint mobile app.
Unlike SharePoint Add-ins, SharePoint Framework client-side web parts are not isolated in an iframe. As a consequence, whatever resources the particular client-side web part has access to,
other elements on the page can access as well. This is important to keep in mind when building solutions that rely on OAuth implicit flow with bearer access tokens or use cookies or browser
storage for storing sensitive information. Because client-side web parts run as a part of the page, other elements on that page are able to access all these resources as well.

Use any library to build your web part


When building customizations using the Script Editor web part, you might have used libraries such as jQuery or Knockout to make your customization dynamic and better respond to user
interaction. When building SharePoint Framework client-side web parts, you can use any client-side library to enrich your solution, the same way you would have done it in the past. An
additional benefit that the SharePoint Framework offers you is isolation of your script. Even though the web part is executed as a part of the page, its script is packaged as a module allowing,
for example, different web parts on the same page to use a different version of jQuery without colliding with each other. This is a powerful feature that allows you to focus on delivering
business value instead of technical migrations and compromising on functionality.

Run as the current user


In customizations built using the Script Editor web part, whenever you needed to communicate with SharePoint, all you had to do was to call its API. The client-side solution was running in
the browser in the context of the current user, and by automatically sending the authentication cookie along with the request, your solution could directly connect to SharePoint. No
additional authentication, such as when using SharePoint Add-ins, was necessary to communicate with SharePoint. The same mechanism applies to customizations built on the SharePoint
Framework that also run under the context of the currently authenticated user and also don't require any additional authentication steps to communicate with SharePoint.

Use only client-side code


Both SharePoint Framework and Script Editor web part solutions run in the browser and can contain only client-side JavaScript code. Client-side solutions have a number of limitations, such
as not being able to elevate permissions in SharePoint or reach out to external APIs that don't support cross-origin communication (CORS) or authentication using OAuth implicit flow. In
such cases, the client-side solution could leverage a remote server-side API to perform the necessary operation and return the results to the client.

Host solution in SharePoint


One of the benefits of building SharePoint customizations using Script Editor web parts was the fact that their code could be hosted in a regular SharePoint document library. Compared to
SharePoint Add-ins, it required less infrastructure and simplified hosting the solution. Additionally, organizations could use SharePoint permissions to control access to the solution files.
While hosting SharePoint Framework solutions on a CDN offers a number of advantages, it is not required, and you could choose to host their code in a regular SharePoint document
library. SharePoint Framework packages (.sppkg files) deployed to the app catalog specify the URL at which SharePoint Framework can find the solution's code. There are no restrictions with
regards to what that URL must be, as long as the user browsing the page with the web part on it can download the script from the specified location.
Office 365 offers the public CDN capability that allows you to publish files from a specific SharePoint document library to a CDN. Office 365 public CDN strikes a nice balance between the
benefits of using a CDN and the simplicity of hosting code files in a SharePoint document library. If your organization doesn't mind their code files being publicly available, using the Office
365 public CDN is an option worth considering.

Differences between SharePoint Framework solutions and Script Editor web part customizations
SharePoint customizations built using the SharePoint Framework and Script Editor web part are very similar. They all run as a part of the page under the context of the current user and are
built using client-side JavaScript. But there are also some significant differences that might influence your architectural decisions and which you should keep in mind when designing your
solution.

Run in no-script sites


When building client-side customizations using the Script Editor web part, you had to take into account whether the site where the customization would be used was a no-script site or not. If
the site was a no-script site, you either had to request the admin to disable the no-script setting or build your solution differently, for example, by using the add-in model.
Because SharePoint Framework solutions are deployed through the app catalog with a prior approval, they are not subject to the no-script restrictions and work on all sites.

Deployed and updated through IT


Script Editor web parts are used to build SharePoint customizations primarily by citizen developers. With nothing more than site owner permissions, citizen developers can build compelling
SharePoint customizations that add business value. Whenever the customization needs to be updated, users with the necessary permissions can apply updates to the solution's script files
and the changes are immediately visible to all users.
Script Editor web part solutions make it hard for IT organizations to keep track of what customizations are being used and where they are being used. Additionally, organizations can't tell
which external scripts are being used in their intranet and have access to their data.
SharePoint Framework gives the control back to the IT. Because SharePoint Framework solutions are deployed and managed centrally in the app catalog, organizations have the opportunity
to review the solution before deploying it. Additionally, any updates are deployed via the app catalog as well, allowing organizations to verify and approve them before deployment.

Focus more on uniform user experience


When building customizations using the Script Editor web part, citizen developers owned the complete DOM of their customization. There were no guidelines related to the user experience
and how such customization should work. As a result, different developers built customizations in different ways, which led to an inconsistent user experience for end users.
One of the goals of the SharePoint Framework is to standardize building client-side customizations so that they are uniform from the deployment, maintenance, and user experience point of
view. Using Office UI Fabric, developers can more easily make their custom solutions look and behave like they are an integral part of SharePoint, simplifying user adoption. SharePoint
Framework toolchain generates package files for the solutions that are deployed to the app catalog, and script bundles that are deployed to the hosting location of your choice. Every solution
is structured and managed in the same way.

Don't modify DOM outside of the customization


Script Editor web parts were frequently used in the past to modify parts of the page, such as adding buttons to the toolbar or changing the heading or branding of the page. Such
customizations relied on the existence of specific DOM elements, and whenever SharePoint UI would be updated, there was a chance that such customization would break.
SharePoint Framework encourages a more structured and reliable approach to customizing SharePoint. Rather than using specific DOM elements to customize SharePoint, SharePoint
Framework provides developers with a public API that they can use to extend SharePoint. Client-side web parts are the only shape supported by the SharePoint Framework, but other
shapes, such as equivalents of JSLink and User Custom Actions, are being taken into consideration for the future so that developers can implement the most common customization
scenarios by using the SharePoint Framew../ork.

Distributed as packages
SharePoint client-side customizations often used SharePoint lists and libraries to store their data. When built using the Script Editor web part, there was no easy way to automatically
provision the necessary prerequisites. Deploying the same customization to another site often meant copying the web part but also correctly recreating and maintaining the necessary data
storage.
SharePoint Framework solutions are distributed as packages capable of provisioning their prerequisites, such as fields, content types, or lists, automatically. Developers building the package
can specify which artifacts are required by the solution, and whenever it's installed in a site, the specified artifacts are created. This significantly simplifies the process of deploying and
managing the solution on multiple sites.

Use TypeScript for building more robust and easier to maintain solutions
When building customizations using the Script Editor web part, citizen developers often used plain JavaScript. Often such solutions didn't contain any automated tests, and refactoring the
code was error-prone.
SharePoint Framework allows developers to benefit from the TypeScript type system when building solutions. Thanks to the type system, errors are caught during development rather than
on runtime. Also, refactoring code can be done more easily as changes to the code are safeguarded by TypeScript. Because all JavaScript is valid TypeScript, the entry barrier is low and you
can migrate your plain JavaScript to TypeScript gradually over time increasing the maintainability of your solution.

Can't rely on the spPageContextInfo object


When building reusable client-side customizations, in the past developers used the spPageContextInfo JavaScript object to get information about the current page, site, or user. This object
offered them an easy way to make their solution reusable across the different sites in SharePoint and not have to use fixed URLs.
While the spPageContextInfo object is still present on classic SharePoint pages, it cannot be reliably used with modern SharePoint pages and libraries. When building solutions on the
SharePoint Framework, developers are recommended to use the IWebPartContext.pageContext object instead, which contains the context information for the particular solution.
No access to SharePoint JavaScript Object Model by default
When building client-side customizations for the classic SharePoint user experience, many developers used the JavaScript Object Model (JSOM) to communicate with SharePoint. JSOM
offered them intellisense and easy access to the SharePoint API in a way similar to the Client-Side Object Model (CSOM). In classic SharePoint pages, the core piece of JSOM was by default
present on the page, and developers could load additional pieces to communicate with SharePoint Search, for example.
The modern SharePoint user experience doesn't include SharePoint JSOM by default. While developers can load it themselves, the recommendation is to use the REST API instead. Using
REST is more universal and interchangeable between the different client-side libraries such as jQuery, Angular, or React.
Microsoft is not actively investing in JSOM anymore. If you prefer working with an API, you could alternatively use the SharePoint Patterns and Practices JavaScript Core Library, which
offers you a fluent API and TypeScript typings.

Migrate existing customization to the SharePoint Framework


Migrating existing Script Editor web part customizations to the SharePoint Framework offers both end-users and developers a number of benefits. When considering migrating existing
customizations to the SharePoint Framework, you can choose to either reuse as much of the existing scripts as possible or completely rewrite the customization.

Reuse existing scripts


When migrating existing Script Editor web part customizations to the SharePoint Framework, you can choose to reuse your existing scripts. Even though the SharePoint Framework
encourages using TypeScript, you can use plain JavaScript and gradually transform it to TypeScript. If you need to support a particular solution for a limited period of time or have a limited
budget, this approach might be good enough for you.
Reusing existing scripts in a SharePoint Framework solution is not always possible. SharePoint Framework solutions, for example, are packaged as JavaScript modules and load
asynchronously on a page. Some JavaScript libraries don't work correctly when referenced in a module or have to be referenced in a specific way. Additionally, relying on page events such as
onload or using the jQuery ready function might lead to undesirable results.

Given the variety of JavaScript libraries, there is no easy way to tell upfront if your existing scripts can be reused in a SharePoint Framework solution or if you need to rewrite it after all. The
only way to determine this is by trying to move the different pieces to a SharePoint Framework solution and see if they work as expected.

Rewrite the customization


If you need to support your solution for a longer period of time, would like to make better use of the SharePoint Framework, or if it turns out that your existing scripts cannot be reused with
the SharePoint Framework, you might need to completely rewrite your customization. While it's more costly than reusing existing scripts, it offers you better results over a longer period of
time: a solution that is better performing, and easier to maintain and use.
When rewriting an existing Script Editor web part customization to a SharePoint Framework solution, you should start with the desired functionality in mind. For implementing the user
experience, you should consider using the Office UI Fabric so that your solution looks like an integral part of SharePoint. For specific components such as charts or sliders, you should try
looking for modern libraries that are distributed as modules and have TypeScript typings. This makes it easier for you to integrate the component in your solution.
While there is no single answer as to which component is the best to use for which scenario, the SharePoint Framework is flexible enough to accommodate most popular scenarios, and you
should be able to transform your existing client-side customizations into fully-featured SharePoint Framework solutions.

Migration tips
When transforming existing Script Editor web part customizations to the SharePoint Framework, there are a few common patterns.
Move existing code to SharePoint Framework
SharePoint customizations built using the Script Editor web part often consist of some HTML markup, included in the web part, and one or more references to JavaScript files. When
transforming your existing customization to a SharePoint Framework solution, the HTML markup from the Script Editor web part would most likely have to be moved to the render method
of the SharePoint Framework client-side web part. References to external scripts would be changed to references in the externals property in the config.json file. Internal scripts would be
copied to the web part source folder and referenced from the web part class by using require() statements.
Reference functions from script files
To reference functions from script files, these functions need to be defined as an export. Consider an existing JavaScript file that you would like to use in a SharePoint Framework client-side
web part:

var greeting = function() {


alert('How are you doing?');
return false;
}

To be able to call the greeting function from the web part class, you would need to change the JavaScript file to:

var greeting = function() {


alert('How are you doing?');
return false;
}

module.exports = {
greeting: greeting
};

Then, in the web part class, you can refer to the script and call the greeting function:

public render(): void {


this.domElement.innerHTML = `
<input type="button" value="Click me"/>`;

const myScript = require('./my-script.js');


this.domElement.querySelector('input').addEventListener('click', myScript.greeting);
}

Execute AJAX calls


Many client-side customizations use jQuery for executing AJAX requests for its simplicity and cross-browser compatibility. If this is all that you're using jQuery for, you can execute the AJAX
calls by using the standard HTTP client provided with the SharePoint Framework.
SharePoint Framework offers you two types of HTTP client: the SPHttpClient, meant for executing requests to the SharePoint REST API, and the HttpClient designed for issuing web
requests to other REST APIs. Here is how you would execute a call by using the SPHttpClient to get the title of the current SharePoint site:
this.context.spHttpClient.get(`${this.context.pageContext.web.absoluteUrl}/_api/web?$select=Title`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
'odata-version': ''
}
})
.then((response: SPHttpClientResponse): Promise<{Title: string}> => {
return response.json();
})
.then((web: {Title: string}): void => {
// web.Title
}, (error: any): void => {
// error
});

See also
Migrate SharePoint JavaScript customizations to SharePoint Framework – reference functions from script files
Migrate SharePoint JavaScript customizations to SharePoint Framework – jQuery AJAX calls and showing data
Migrate jQuery and DataTables solution built using Script Editor web part to SharePoint
Framework
5/3/2018 • 15 minutes to read Edit Online

One of the frequently used jQuery plug-ins is DataTables. With DataTables, you can easily build powerful data overviews of data coming from both SharePoint and external APIs.

List of IT requests built using the Script Editor web part


To illustrate the process of migrating a SharePoint customization using DataTables to the SharePoint Framework, use the following solution that shows an overview of IT support requests
retrieved from a SharePoint list.

The solution is built by using the standard SharePoint Script Editor web part. Following is the code used by the customization.
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://cdn.datatables.net/1.10.15/js/jquery.dataTables.js"></script>
<script src="https://momentjs.com/downloads/moment.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table id="requests" class="display" cellspacing="0" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Business unit</th>
<th>Category</th>
<th>Status</th>
<th>Due date</th>
<th>Assigned to</th>
</tr>
</thead>
</table>
<script>
// UMD
(function(factory) {
"use strict";

if (typeof define === 'function' && define.amd) {


// AMD
define(['jquery'], function ($) {
return factory( $, window, document );
});
}
else if (typeof exports === 'object') {
// CommonJS
module.exports = function (root, $) {
if (!root) {
root = window;
}

if (!$) {
$ = typeof window !== 'undefined' ?
require('jquery') :
require('jquery')( root );
}

return factory($, root, root.document);


};
}
else {
// Browser
factory(jQuery, window, document);
}
}
(function($, window, document) {
$.fn.dataTable.render.moment = function (from, to, locale) {
// Argument shifting
if (arguments.length === 1) {
locale = 'en';
to = from;
from = 'YYYY-MM-DD';
}
else if (arguments.length === 2) {
locale = 'en';
}

return function (d, type, row) {


var m = window.moment(d, from, locale, true);

// Order and type get a number value from Moment, everything else
// sees the rendered value
return m.format(type === 'sort' || type === 'type' ? 'x' : to);
};
};
}));
</script>
<script>
$(document).ready(function() {
$('#requests').DataTable({
'ajax': {
'url': "../_api/web/lists/getbytitle('IT Requests')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title",
'headers': { 'Accept': 'application/json;odata=nometadata' },
'dataSrc': function(data) {
return data.value.map(function(item) {
return [
item.ID,
item.BusinessUnit,
item.Category,
item.Status,
new Date(item.DueDate),
item.AssignedTo.Title
];
});
}
},
columnDefs: [{
targets: 4,
render: $.fn.dataTable.render.moment('YYYY/MM/DD')
}]
});
});
</script>

First, the customization loads the libraries it uses: jQuery, DataTables, and Moment.js (lines 1-4).
Next, it specifies the structure of the table used to present the data (lines 5-16).
After creating the table, it wraps Moment.js into a DataTables plug-in so that dates displayed in the table can be formatted (first script block on lines 17-70).
Finally, the customization uses DataTables to load and present the list of IT support requests. The data is loaded by using AJAX from a SharePoint list (lines 71-96).
Thanks to using DataTables, end users get a powerful solution where they can easily filter, sort, and page through the results without any additional development effort.

Migrate the IT requests overview solution from the Script Editor web part to the SharePoint Framework
NOTE

Before following the steps in this article, be sure to set up your development environment for building SharePoint Framework solutions.
Transforming this customization to the SharePoint Framework offers a number of benefits such as more user-friendly configuration and centralized management of the solution. Following is
a step-by-step description of how you would migrate the solution to the SharePoint Framework. First, you will migrate the solution to the SharePoint Framework with as few changes to the
original code as possible. Later, you will transform the solution's code to TypeScript to benefit from its development-time type safety features.
NOTE

The source code of the project in the different stages of migration is available at Tutorial: Migrate jQuery and DataTables solution built using Script Editor web part to SharePoint Framework

Create new SharePoint Framework project


1. Start by creating a new folder for your project:

md datatables-itrequests

2. Navigate to the project folder:

cd datatables-itrequests

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, define values as follows:


datatables-itrequests as your solution name
Use the current folder for the location to place the files
No javaScript web framework as the starting point to build the web part
IT requests as your web part name
Shows overview of IT support requests as your web part description
5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. In this tutorial, you will use Visual Studio Code.

Load JavaScript libraries


Similar to the original solution built using the Script Editor web part, first you need to load the JavaScript libraries required by the solution. In SharePoint Framework this usually consists of
two steps: specifying the URL from which the library should be loaded, and referencing the library in the code.
1. Specify the URLs from which libraries should be loaded. In the code editor, open the ./config/config.json file, and change the externals section to:

{
"externals": {
"jquery": "https://code.jquery.com/jquery-1.12.4.min.js",
"datatables.net": "https://cdn.datatables.net/1.10.15/js/jquery.dataTables.min.js",
"moment": "https://momentjs.com/downloads/moment.min.js"
}
}

2. Open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and after the last import statement add:

import 'jquery';
import 'datatables.net';
import 'moment';

Define data table


Just as in the original solution, the next step is to define the structure of the table used to display the data.
In the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and change the render method to:
export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
public render(): void {
this.domElement.innerHTML = `
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table id="requests" class="display ${styles.helloWorld}" cellspacing="0" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Business unit</th>
<th>Category</th>
<th>Status</th>
<th>Due date</th>
<th>Assigned to</th>
</tr>
</thead>
</table>`;
}
// ...
}

Register Moment.js plugin for DataTables


The next step is to define the Moment.js plug-in for DataTables so that dates in the table can be formatted.
1. In the ./src/webparts/itRequests folder, create a new file named moment-plugin.js, and paste the following code:

// UMD
(function (factory) {
"use strict";

if (typeof define === 'function' && define.amd) {


// AMD
define(['jquery'], function ($) {
return factory($, window, document);
});
}
else if (typeof exports === 'object') {
// CommonJS
module.exports = function (root, $) {
if (!root) {
root = window;
}

if (!$) {
$ = typeof window !== 'undefined' ?
require('jquery') :
require('jquery')(root);
}

return factory($, root, root.document);


};
}
else {
// Browser
factory(jQuery, window, document);
}
}

(function ($, window, document) {


$.fn.dataTable.render.moment = function (from, to, locale) {
// Argument shifting
if (arguments.length === 1) {
locale = 'en';
to = from;
from = 'YYYY-MM-DD';
}
else if (arguments.length === 2) {
locale = 'en';
}

return function (d, type, row) {


var moment = require('moment');
var m = moment(d, from, locale, true);

// Order and type get a number value from Moment, everything else
// sees the rendered value
return m.format(type === 'sort' || type === 'type' ? 'x' : to);
};
};
}));

2. For the web part to load the plug-in, it has to reference the newly created moment-plugin.js file. In the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file,
and after the last import statement add:

import './moment-plugin';

NOTE

You don't need to include the .js extension when referencing other files. SharePoint Framework automatically resolves the extension for you.

Initiate DataTables and load data


The last step is to include the code that initiates the data table and loads the data from SharePoint.
1. In the ./src/webparts/itRequests folder, create a new file named script.js, and paste the following code:
$(document).ready(function () {
$('#requests').DataTable({
'ajax': {
'url': "../../_api/web/lists/getbytitle('IT Requests')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title",
'headers': { 'Accept': 'application/json;odata=nometadata' },
'dataSrc': function (data) {
return data.value.map(function (item) {
return [
item.ID,
item.BusinessUnit,
item.Category,
item.Status,
new Date(item.DueDate),
item.AssignedTo.Title
];
});
}
},
columnDefs: [{
targets: 4,
render: $.fn.dataTable.render.moment('YYYY/MM/DD')
}]
});
});

2. To reference this file in the web part, in the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and change the render method to:

export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {


public render(): void {
this.domElement.innerHTML = `
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table id="requests" class="display ${styles.helloWorld}" cellspacing="0" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Business unit</th>
<th>Category</th>
<th>Status</th>
<th>Due date</th>
<th>Assigned to</th>
</tr>
</thead>
</table>`;

require('./script');
}
// ...
}

3. Verify that the web part is working as expected in the command line by executing:

gulp serve --nobrowser

Because the web part loads its data from SharePoint, you have to test the web part by using the hosted SharePoint Framework Workbench. Navigate to
https://yourtenant.sharepoint.com/_layouts/workbench.aspx and add the web part to the canvas. You should now see the IT requests displayed by using the DataTables jQuery plug-in.

Add support for configuring the web part through web part properties
In the previous steps, you migrated the IT requests solutions from the Script Editor web part to the SharePoint Framework. While the solution already works as expected, it doesn't use any
of the SharePoint Framework benefits. The name of the list from which IT requests are loaded is included in the code, and the code itself is plain JavaScript, which is harder to refactor than
TypeScript.
The following steps illustrate how to extend the existing solution to allow users to specify the name of the list to load the data from. Later, you transform the code to TypeScript to benefit
from its type safety features.
Define web part property for storing the name of the list
1. Define a web part property to store the name of the list from which IT requests should be loaded. In the code editor, open the
./src/webparts/itRequests/ItRequestsWebPart.manifest.json file, and rename the default description property to listName and clear its value.

2. Update the web part properties interface to reflect the changes in the manifest. In the code editor, open the ./src/webparts/itRequests/IItRequestsWebPartProps.ts file, and
change its contents to:

export interface IItRequestsWebPartProps {


listName: string;
}

3. Update the display labels for the listName property. Open the ./src/webparts/itRequests/loc/mystrings.d.ts file, and change its contents to:

declare interface IItRequestsStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
}

declare module 'itRequestsStrings' {


const strings: IItRequestsStrings;
export = strings;
}

4. Open the ./src/webparts/itRequests/loc/en-us.js file, and change its contents to:

define([], function() {
return {
"PropertyPaneDescription": "IT Requests settings",
"BasicGroupName": "Data",
"ListNameFieldLabel": "List name"
}
});

5. Update the web part to use the newly defined property. In the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and change the
getPropertyPaneConfiguration method to:
export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {
// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}

protected get disableReactivePropertyChanges(): boolean {


return true;
}
}

To prevent the web part from reloading as users type the name of the list, you also configured the web part to use the non-reactive property pane by adding the
disableReactivePropertyChanges method and setting its return value to true.

Use the configured name of the list to load the data from
Initially, the name of the list from which the data should be loaded was embedded in the REST query. Now that users can configure this name, the configured value should be injected into
the REST query before loading the data. The easiest way to do that is by moving the contents of the script.js file to the main web part file.
1. In the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and change the render method to:

var $: any = (window as any).$;

export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {


public render(): void {
this.domElement.innerHTML = `
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table class="display ${styles.helloWorld}" cellspacing="0" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Business unit</th>
<th>Category</th>
<th>Status</th>
<th>Due date</th>
<th>Assigned to</th>
</tr>
</thead>
</table>`;

$(document).ready(() => {
$('table', this.domElement).DataTable({
'ajax': {
'url': `../../_api/web/lists/getbytitle('${escape(this.properties.listName)}')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title`,
'headers': { 'Accept': 'application/json;odata=nometadata' },
'dataSrc': function (data) {
return data.value.map(function (item) {
return [
item.ID,
item.BusinessUnit,
item.Category,
item.Status,
new Date(item.DueDate),
item.AssignedTo.Title
];
});
}
},
columnDefs: [{
targets: 4,
render: $.fn.dataTable.render.moment('YYYY/MM/DD')
}]
});
});
}

// ...
}

2. Instead of referencing the code from the script.js file, all of its contents are a part of the web part's render method. In the REST query, in line 40, you can now replace the fixed name
of the list with the value of the listName property which holds the name of the list as configured by the user. Before using the value, it's being escaped by using the lodash's escape
function to disallow script injection.
At this point, the bulk of the code is still written using plain JavaScript. To avoid build issues with the $ jQuery variable, you had to define it as any type in line 18. Later, when
transforming the code to TypeScript, you replace it with a proper type definition.
As you have just moved the contents of the script.js file into the main web part file, the script.js is no longer necessary, and you can delete it from the project.
3. To verify that the web part is working as expected, run the following in the command line:
gulp serve --nobrowser

4. Navigate to the hosted Workbench and add the web part to the canvas. Open the web part property pane, specify the name of the list with IT requests, and select the Apply button to
confirm the changes.
You should now see IT requests displayed in the web part.

Transform the plain JavaScript code to TypeScript


Using TypeScript over plain JavaScript offers a number of benefits. Not only is TypeScript easier to maintain and refactor, but it also allows you to catch errors earlier. The following steps
describe how you would transform the original JavaScript code to TypeScript.

Add type definitions for used libraries


To function properly, TypeScript requires type definitions for the different libraries used in the project. Type definitions are often distributed as npm packages in the @types namespace.
1. Install type definitions for jQuery and DataTables by executing the following in the command line:

npm install --save-dev @types/jquery @types/jquery.datatables

Type definitions for Moment.js are distributed together with the Moment.js package. Even though you're loading Moment.js from a URL, in order to use its typings, you still need to
install the Moment.js package in the project.
2. Install the Moment.js package by executing the following in the command line:

npm install --save moment

Update package references


To use types from the installed type definitions, you have to change how you reference libraries.
1. In the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and change the import 'jquery'; statement to:

import * as $ from 'jquery';

2. Having defined $ as jQuery, you can now remove the local definition of $ that you added previously:

var $: any = (window as any).$;

3. Because DataTables is a jQuery plug-in that attaches itself to jQuery, you cannot load its type definition directly. Instead, you have to add it to the list of types loaded globally. In the
code editor, open the ./tsconfig.json file, and to the types array, add jquery.datatables:
{
"compilerOptions": {
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "commonjs",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"types": [
"es6-promise",
"es6-collections",
"jquery.datatables",
"webpack-env"
]
}
}

Update main web part files to TypeScript


Now that you have type definitions for all libraries installed in the project, you can start transforming the plain JavaScript code to TypeScript.
1. Define an interface for the IT request information that you retrieve from the SharePoint list. In the code editor, open the ./src/webparts/itRequests/ItRequestsWebPart.ts file, and
just above the web part class, add the following code snippet:

interface IRequestItem {
ID: number;
BusinessUnit: string;
Category: string;
Status: string;
DueDate: string;
AssignedTo: { Title: string; };
}

2. Next, in the web part class, change the render method to:

export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {


public render(): void {
this.domElement.innerHTML = `
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.15/css/jquery.dataTables.min.css" />
<table class="display ${styles.helloWorld}" cellspacing="0" width="100%">
<thead>
<tr>
<th>ID</th>
<th>Business unit</th>
<th>Category</th>
<th>Status</th>
<th>Due date</th>
<th>Assigned to</th>
</tr>
</thead>
</table>`;

$('table', this.domElement).DataTable({
'ajax': {
'url': `../../_api/web/lists/getbytitle('${escape(this.properties.listName)}')/items?$select=ID,BusinessUnit,Category,Status,DueDate,AssignedTo/Title&$expand=AssignedTo/Title`,
'headers': { 'Accept': 'application/json;odata=nometadata' },
'dataSrc': (data: { value: IRequestItem[] }): any[][] => {
return data.value.map((item: IRequestItem): any[] => {
return [
item.ID,
item.BusinessUnit,
item.Category,
item.Status,
new Date(item.DueDate),
item.AssignedTo.Title
];
});
}
},
columnDefs: [{
targets: 4,
render: $.fn.dataTable.render.moment('YYYY/MM/DD')
}]
});
}

// ...
}

3. Notice how the AJAX request, to retrieve the data from the SharePoint list, is now typed and helps you ensure you're referring to correct properties when passing them into an array
to DataTables. The data structure used by DataTables to represent a row in the table is an array of mixed types, so for simplicity it was defined as any[]. Using the any type in this
context is not bad, because the data returned inside the dataSrc property is used internally by DataTables.
As you're updating the render method, you have also added two more changes. First, you removed the id attribute from the table. This allows you to place multiple instances of the
same web part on the page. Also, you removed the reference to the $(document).ready() function, which isn't necessary because the DOM of the element where the data table is
rendered is set before the DataTables initiation code.

Update the Moment.js DataTables plugin to TypeScript


The last piece of the solution that needs to be transformed to TypeScript is the Moment.js DataTables plug-in.
1. Rename the ./src/webparts/itRequests/moment-plugin.js file to ./src/webparts/itRequests/moment-plugin.ts so that it is processed by the TypeScript compiler.
2. Open the moment-plugin.ts file in the code editor, and replace its contents with:
import * as $ from 'jquery';
import * as moment from 'moment';

/* tslint:disable:no-function-expression */
$.fn.dataTable.render.moment = function (from: string, to: string, locale: string): (d: any, type: string, row: any) => string {
/* tslint:enable */
// Argument shifting
if (arguments.length === 1) {
locale = 'en';
to = from;
from = 'YYYY-MM-DD';
}
else if (arguments.length === 2) {
locale = 'en';
}

return (d: any, type: string, row: any): string => {


let m: moment.Moment = moment(d, from, locale, true);

// Order and type get a number value from Moment, everything else
// sees the rendered value
return m.format(type === 'sort' || type === 'type' ? 'x' : to);
};
};

3. You start with loading references to jQuery and Moment.js to let TypeScript know what the corresponding variables refer to. Next, you define the plug-in function. Usually in
TypeScript you use the arrow notation for functions ( => ). In this case, however, because you need access to the arguments property, you have to use the regular function definition.
To prevent tslint from reporting a warning about not using the arrow notation, you can explicitly disable the no-function-expression rule around the function definition.
4. To confirm that everything is working as expected, in the command line, execute:

gulp serve --nobrowser

5. Navigate to the hosted Workbench and add the web part to the canvas. Although visually nothing has changed, the new code base uses TypeScript and its type definitions to help you
maintain the solution.
Migrate jQuery and FullCalendar solution built using Script Editor web part to SharePoint
Framework
5/3/2018 • 24 minutes to read Edit Online

When building SharePoint solutions, SharePoint developers often use the FullCalendar jQuery plug-in to display data in calendar view. FullCalendar is a great alternative to the standard
SharePoint calendar view, as it allows you to render as calendar data from multiple calendar lists, data from non-calendar lists, or even data from outside SharePoint. This article illustrates
how you would migrate a SharePoint customization by using FullCalendar built with the Script Editor web part to the SharePoint Framework.

List of tasks displayed as a calendar built using the Script Editor web part
To illustrate the process of migrating a SharePoint customization using FullCalendar to the SharePoint Framework, you will use the following solution that shows a calendar view of tasks
retrieved from a SharePoint list.

The solution is built using the standard SharePoint Script Editor web part. Following is the code used by the customization.

<script src="//code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"></script>
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>

<script>
var PATH_TO_DISPFORM = _spPageContextInfo.webAbsoluteUrl + "/Lists/Tasks/DispForm.aspx";
var TASK_LIST = "Tasks";
var COLORS = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];

displayTasks();

function displayTasks() {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: function (calEvent, jsEvent, view) {
eventClick: function (calEvent, jsEvent, view) {
window.location = PATH_TO_DISPFORM + "?ID=" + calEvent.id;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: function (event, delta, revertFunc) {
updateTask(event.id, event.start, event.end);
},
// put the events on the calendar
events: function (start, end, timezone, callback) {
var startDate = start.format('YYYY-MM-DD');
var endDate = end.format('YYYY-MM-DD');

var restQuery = "/_api/Web/Lists/GetByTitle('" + TASK_LIST + "')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '" + startDate + "' and DueDate le '" + endDate + "')or(StartDate ge '" + startDate + "' and StartDate le '" + endDate + "'))";

$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done(function (data, textStatus, jqXHR) {
var personColors = {};
var colorNo = 0;

var events = data.value.map(function (task) {


var assignedTo = task.AssignedTo.map(function (person) {
return person.Title;
}).join(', ');

var color = personColors[assignedTo];


if (!color) {
color = COLORS[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= COLORS.length) {
colorNo = 0;
}

return {
title: task.Title + " - " + assignedTo,
id: task.ID,
color: color, // specify the background color and border color can also create a class and use className parameter.
start: moment.utc(task.StartDate).add("1", "days"),
end: moment.utc(task.DueDate).add("1", "days") // add one day to end date so that calendar properly shows event ending on that day
};
});

callback(events);
});
}
});
}

function updateTask(id, startDate, dueDate) {


// subtract the previously added day to the date to store correct date
var sDate = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
var dDate = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";

$.ajax({
url: _spPageContextInfo.webAbsoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then(function (data, textStatus, jqXHR) {
return $.ajax({
url: _spPageContextInfo.webAbsoluteUrl +
"/_api/Web/Lists/getByTitle('" + TASK_LIST + "')/Items(" + id + ")",
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done(function (data, textStatus, jqXHR) {
alert("Update Successful");
})
.fail(function (jqXHR, textStatus, errorThrown) {
alert("Update Failed");
})
.always(function () {
displayTasks();
});
}
</script>
</script>

NOTE

This solution is based on the work of Mark Rackley, Office Servers and Services MVP and Chief Strategy Officer at PAIT Group. For more information about the original solution, see Using
FullCalendar.io to Create Custom Calendars in SharePoint.
First, the customization loads the libraries it uses: jQuery, Moment.js, and FullCalendar (lines 1-4).
Next, it defines the div into which the generated calendar view is injected (line 5).
It then defines two functions: displayTasks, used to display tasks in the calendar view, and updateTask, which is triggered after dragging and dropping a task to a different date and which
updates the dates on the underlying list item. Each function defines its own REST query, which is then used to communicate with the SharePoint List REST API to retrieve or update list
items.
Using the FullCalendar jQuery plug-in, with little effort users get rich solutions capable of things such as using different colors to mark different events or using drag and drop to reorganize
events.

Migrate the Tasks calendar solution from the Script Editor web part to the SharePoint Framework
NOTE

Before following the steps in this article, be sure to set up your development environment for building SharePoint Framework solutions.
Transforming a Script Editor web part-based customization to the SharePoint Framework offers a number of benefits such as more user-friendly configuration and centralized management
of the solution. Following is a step-by-step description of how you would migrate the solution to the SharePoint Framework.
First, you migrate the solution to the SharePoint Framework with as few changes to the original code as possible. Later, you transform the solution's code to TypeScript to benefit from its
development-time type safety features, and replace some of the code with the SharePoint Framework API to fully benefit from its capabilities and simplify the solution even further.
NOTE

The source code of the project in the different stages of migration is available at Tutorial: Migrate jQuery and FullCalendar solution built using Script Editor web part to SharePoint
Framework.

Create new SharePoint Framework project


1. Start by creating a new folder for your project:

md fullcalendar-taskscalendar

2. Navigate to the project folder:


cd fullcalendar-taskscalendar

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, define values as follows:


fullcalendar-taskscalendar as your solution name
Use the current folder for the location to place the files
WebPart as the client-side component to create
Tasks calendar as your web part name
Shows tasks in the calendar view as your web part description
No javaScript web framework as the starting point to build the web part

5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. In this tutorial, you will use Visual Studio Code.

Load JavaScript libraries


Similar to the original solution built by using the Script Editor web part, you first need to load the JavaScript libraries required by the solution. In SharePoint Framework, this usually consists
of two steps: specifying the URL from which the library should be loaded, and referencing the library in the code.
1. Specify the URLs from which libraries should be loaded. In the code editor, open the ./config/config.json file, and change the externals section to:

{
"externals": {
"jquery": "https://code.jquery.com/jquery-1.11.1.min.js",
"moment": "https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js",
"fullcalendar": "https://cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.js"
}
}

2. Open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file, and after the last import statement, add:

import 'jquery';
import 'moment';
import 'fullcalendar';

Define container div


Just as in the original solution, the next step is to define the location where the calendar should be rendered.
In the code editor, open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file, and change the render method to:

export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {


public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;
}
// ...
}

Initiate FullCalendar and load data


The last step is to include the code that initiates the FullCalendar jQuery plug-in and loads the data from SharePoint.
1. In the ./src/webparts/tasksCalendar folder, create a new file named script.js, and paste in the following code:

var moment = require('moment');

var PATH_TO_DISPFORM = window.webAbsoluteUrl + "/Lists/Tasks/DispForm.aspx";


var TASK_LIST = "Tasks";
var COLORS = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];

displayTasks();

function displayTasks() {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: function (calEvent, jsEvent, view) {
window.location = PATH_TO_DISPFORM + "?ID=" + calEvent.id;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: function (event, delta, revertFunc) {
updateTask(event.id, event.start, event.end);
},
// put the events on the calendar
events: function (start, end, timezone, callback) {
var startDate = start.format('YYYY-MM-DD');
var endDate = end.format('YYYY-MM-DD');

var restQuery = "/_api/Web/Lists/GetByTitle('" + TASK_LIST + "')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '" + startDate + "' and DueDate le '" + endDate + "')or(StartDate ge '" + startDate + "' and StartDate le '" + endDate + "'))";

$.ajax({
url: window.webAbsoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done(function (data, textStatus, jqXHR) {
var personColors = {};
var colorNo = 0;

var events = data.value.map(function (task) {


var assignedTo = task.AssignedTo.map(function (person) {
return person.Title;
}).join(', ');

var color = personColors[assignedTo];


if (!color) {
if (!color) {
color = COLORS[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= COLORS.length) {
colorNo = 0;
}

return {
title: task.Title + " - " + assignedTo,
id: task.ID,
color: color, // specify the background color and border color can also create a class and use className parameter.
start: moment.utc(task.StartDate).add("1", "days"),
end: moment.utc(task.DueDate).add("1", "days") // add one day to end date so that calendar properly shows event ending on that day
};
});

callback(events);
});
}
});
}

function updateTask(id, startDate, dueDate) {


// subtract the previously added day to the date to store correct date
var sDate = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
var dDate = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";

$.ajax({
url: window.webAbsoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then(function (data, textStatus, jqXHR) {
return $.ajax({
url: window.webAbsoluteUrl +
"/_api/Web/Lists/getByTitle('" + TASK_LIST + "')/Items(" + id + ")",
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done(function (data, textStatus, jqXHR) {
alert("Update Successful");
})
.fail(function (jqXHR, textStatus, errorThrown) {
alert("Update Failed");
})
.always(function () {
displayTasks();
});
}

This code is almost identical to the original code of the Script Editor web part customization. The only difference is that where the original code retrieved the URL of the current web
from the global _spPageContextInfo variable set by SharePoint (lines 8, 45, 96 and 104), the code in the SharePoint Framework uses a custom variable that you have to set in the
web part.
SharePoint Framework client-side web parts can be used both on classic and modern pages. While the _spPageContextInfo variable is present on classic pages, it's not available on
modern pages, which is why you can't rely on it and need a custom property that you can control yourself instead.
2. To reference this file in the web part, in the code editor, open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file, and change the render method to:

export default class ItRequestsWebPart extends BaseClientSideWebPart<IItRequestsWebPartProps> {


public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;

(window as any).webAbsoluteUrl = this.context.pageContext.web.absoluteUrl;


require('./script');
}
// ...
}

3. Verify that the web part is working as expected by executing the following in the command line:

gulp serve --nobrowser

Because the web part loads its data from SharePoint, you have to test the web part by using the hosted SharePoint Framework workbench.
4. Navigate to https://yourtenant.sharepoint.com/_layouts/workbench.aspx and add the web part to the canvas. You should now see the tasks displayed in a calendar view by using the
FullCalendar jQuery plug-in.

Add support for configuring the web part through web part properties
In the previous steps, you migrated the Tasks calendar solutions from the Script Editor web part to the SharePoint Framework. While the solution already works as expected, it doesn't use
any of the SharePoint Framework benefits. The name of the list from which tasks are loaded is included in the code, and the code itself is plain JavaScript, which is harder to refactor than
TypeScript.
The following steps illustrate how to extend the existing solution to allow users to specify the name of the list to load the data from. Later, you transform the code to TypeScript to benefit
from its type safety features.

Define web part property for storing the name of the list
1. Define a web part property to store the name of the list from which tasks should be loaded. In the code editor, open the
./src/webparts/tasksCalendar/TasksCalendarWebPart.manifest.json file, and rename the default description property to listName and clear its value.
2. Update the web part properties interface to reflect the changes in the manifest. In the code editor, open the ./src/webparts/tasksCalendar/ITasksCalendarWebPartProps.ts file,
and change its contents to:

export interface ITasksCalendarWebPartProps {


listName: string;
}

3. Update the display labels for the listName property. Open the ./src/webparts/tasksCalendar/loc/mystrings.d.ts file, and change its contents to:

declare interface ITasksCalendarStrings {


PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
}

declare module 'tasksCalendarStrings' {


const strings: ITasksCalendarStrings;
export = strings;
}

4. Open the ./src/webparts/tasksCalendar/loc/en-us.js file, and change its contents to:

define([], function() {
return {
"PropertyPaneDescription": "Tasks calendar settings",
"BasicGroupName": "Data",
"ListNameFieldLabel": "List name"
}
});

5. Update the web part to use the newly defined property. In the code editor, open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file, and change the
getPropertyPaneConfiguration method to:
export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {
// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('listName', {
label: strings.ListNameFieldLabel
})
]
}
]
}
]
};
}

protected get disableReactivePropertyChanges(): boolean {


return true;
}
}

To prevent the web part from reloading as users type the name of the list, you've also configured the web part to use the non-reactive property pane by adding the
disableReactivePropertyChanges method and setting its return value to true.

Use the configured name of the list to load the data from
Initially, the name of the list from which the data should be loaded was embedded in the REST queries. Now that users can configure this name, the configured value should be injected into
the REST queries before executing them. The easiest way to do that is by moving the contents of the script.js file to the main web part file.
1. In the code editor, open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file.
2. Change the import statement to load the required libraries to:

var $: any = require('jquery');


var moment: any = require('moment');

import 'fullcalendar';

var COLORS = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];

Because Moment.js is referenced in the code that you will be using later, its name must be known to TypeScript or building the project will fail. The same applies to jQuery. Because
FullCalendar is a jQuery plug-in that attaches itself to the jQuery object, it can be imported the same way as previously.
The last part includes copying the list of colors to use for marking the different events.
3. Copy the displayTasks and updateTask functions from the script.js file, and paste them as follows inside the TasksCalendarWebPart class:

export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {


// ...

private displayTasks() {
$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: (calEvent, jsEvent, view) => {
(window as any).location = this.context.pageContext.web.absoluteUrl +
"/Lists/" + escape(this.properties.listName) + "/DispForm.aspx?ID=" + calEvent.id;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: (event, delta, revertFunc) => {
this.updateTask(event.id, event.start, event.end);
},
// put the events on the calendar
events: (start, end, timezone, callback) => {
var startDate = start.format('YYYY-MM-DD');
var endDate = end.format('YYYY-MM-DD');

var restQuery = "/_api/Web/Lists/GetByTitle('" + escape(this.properties.listName) + "')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '" + startDate + "' and DueDate le '" + endDate + "')or(StartDate ge '" + startDate + "' and StartDate le '" + endDate + "'))";

$.ajax({
url: this.context.pageContext.web.absoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done((data, textStatus, jqXHR) => {
var personColors = {};
var colorNo = 0;
var events = data.value.map((task) => {
var assignedTo = task.AssignedTo.map((person) => {
return person.Title;
}).join(', ');

var color = personColors[assignedTo];


if (!color) {
color = COLORS[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= COLORS.length) {
colorNo = 0;
}

return {
title: task.Title + " - " + assignedTo,
id: task.ID,
color: color, // specify the background color and border color can also create a class and use className parameter.
start: moment.utc(task.StartDate).add("1", "days"),
end: moment.utc(task.DueDate).add("1", "days") // add one day to end date so that calendar properly shows event ending on that day
};
});

callback(events);
});
}
});
}

private updateTask(id, startDate, dueDate) {


// subtract the previously added day to the date to store correct date
var sDate = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
var dDate = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";

$.ajax({
url: this.context.pageContext.web.absoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then((data, textStatus, jqXHR) => {
return $.ajax({
url: this.context.pageContext.web.absoluteUrl +
"/_api/Web/Lists/getByTitle('" + escape(this.properties.listName) + "')/Items(" + id + ")",
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done((data, textStatus, jqXHR) => {
alert("Update Successful");
})
.fail((jqXHR, textStatus, errorThrown) => {
alert("Update Failed");
})
.always(() => {
this.displayTasks();
});
}

// ...
}

There are a few changes in the code compared to the previous situation. Plain JavaScript functions are now changed into TypeScript methods by replacing the function keyword with
the private modifier. This is required to be able to add them to the TaskCalendarWebPart class. Because both methods are now in the same file as the web part, instead of defining a
global variable to hold the URL of the current site, you can access it directly from the web part context by using the this.context.pageContext.web.absoluteUrl property. Additionally, in
all REST queries, the fixed list name is replaced with the value of the listName property, which holds the name of the list as configured by the user. Before using the value, it's being
escaped by using the lodash's escape function to disallow script injection.
4. As the last step, change the render method to call the newly added displayTasks method:
export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {
public render(): void {
this.domElement.innerHTML = `
<div class="${styles.tasksCalendar}">
<link type="text/css" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/3.4.0/fullcalendar.min.css" />
<div id="calendar"></div>
</div>`;

this.displayTasks();
}
// ...
}

5. Because you have just moved the contents of the script.js file into the main web part file, the script.js is no longer necessary and you can delete it from the project.
6. To verify that the web part is working as expected, run the following in the command line:

gulp serve --nobrowser

7. Navigate to the hosted Workbench and add the web part to the canvas. Open the web part property pane, specify the name of the list with tasks, and select the Apply button to
confirm the changes. You should now see tasks displayed in a calendar view in the web part.

Transform the plain JavaScript code to TypeScript


Using TypeScript over plain JavaScript offers a number of benefits. Not only is TypeScript easier to maintain and refactor, but it also allows you to catch errors earlier. The following steps
describe how you would transform the original JavaScript code to TypeScript.

Add type definitions for used libraries


To function properly, TypeScript requires type definitions for the different libraries used in the project. Type definitions are often distributed as npm packages in the @types namespace.
1. Install type definitions for jQuery and FullCalendar by executing the following in the command line:

npm install --save-dev @types/jquery@1 @types/fullcalendar

Type definitions for Moment.js are distributed together with the Moment.js package. Even though you're loading Moment.js from a URL, to use its typings, you still need to install the
Moment.js package in the project.
2. Install the Moment.js package by executing the following in the command line:

npm install --save moment


Update package references
To use types from the installed type definitions, you have to change how you reference libraries.
In the code editor, open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file, and change the import statements to:

import * as $ from 'jquery';


import 'fullcalendar';
import * as moment from 'moment';

Update main web part files to TypeScript


Now that you have type definitions for all libraries installed in the project, you can start transforming the plain JavaScript code to TypeScript.
1. Define an interface for a task that you retrieve from the SharePoint list. In the code editor, open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts file, and just above
the web part class, add the following code snippet:

interface ITask {
ID: number;
Title: string;
StartDate: string;
DueDate: string;
AssignedTo: [{ Title: string }];
}

2. In the web part class, change the displayTasks and updateTask methods to:

export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {


private readonly colors: string[] = ['#466365', '#B49A67', '#93B7BE', '#E07A5F', '#849483', '#084C61', '#DB3A34'];

// ...

private displayTasks(): void {


$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: (calEvent: FC.EventObject, jsEvent: MouseEvent, view: FC.ViewObject): void => {
(window as any).location = `${this.context.pageContext.web.absoluteUrl}\
/Lists/${escape(this.properties.listName)}/DispForm.aspx?ID=${calEvent.id}`;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: (event: FC.EventObject, delta: moment.Duration, revertFunc: Function): void => {
this.updateTask(event.id, <moment.Moment>event.start, <moment.Moment>event.end);
},
// put the events on the calendar
events: (start: moment.Moment, end: moment.Moment, timezone: string, callback: Function): void => {
const startDate: string = start.format('YYYY-MM-DD');
const endDate: string = end.format('YYYY-MM-DD');

const restQuery: string = `/_api/Web/Lists/GetByTitle('${escape(this.properties.listName)}')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '${startDate}' and DueDate le '${endDate}')or(StartDate ge '${startDate}' and StartDate le '${endDate}'))`;

$.ajax({
url: this.context.pageContext.web.absoluteUrl + restQuery,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=nometadata"
}
})
.done((data: { value: ITask[] }, textStatus: string, jqXHR: JQueryXHR): void => {
let personColors: { [person: string]: string; } = {};
let colorNo: number = 0;

const events: FC.EventObject[] = data.value.map((task: ITask): FC.EventObject => {


const assignedTo: string = task.AssignedTo.map((person: { Title: string }): string => {
return person.Title;
}).join(', ');

let color: string = personColors[assignedTo];


if (!color) {
color = this.colors[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= this.colors.length) {
colorNo = 0;
}

return {
title: `${task.Title} - ${assignedTo}`,
id: task.ID,
// specify the background color and border color can also create a class and use className parameter
color: color,
start: moment.utc(task.StartDate).add("1", "days"),
// add one day to end date so that calendar properly shows event ending on that day
end: moment.utc(task.DueDate).add("1", "days")
};
});

callback(events);
});
});
}
});
}

private updateTask(id: number, startDate: moment.Moment, dueDate: moment.Moment): void {


// subtract the previously added day to the date to store correct date
const sDate: string = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
const dDate: string = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";

$.ajax({
url: this.context.pageContext.web.absoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then((data: { FormDigestValue: string }, textStatus: string, jqXHR: JQueryXHR): JQueryXHR => {
return $.ajax({
url: `${this.context.pageContext.web.absoluteUrl}\
/_api/Web/Lists/getByTitle('${escape(this.properties.listName)}')/Items(${id})`,
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done((data: {}, textStatus: string, jqXHR: JQueryXHR): void => {
alert("Update Successful");
})
.fail((jqXHR: JQueryXHR, textStatus: string, errorThrown: string): void => {
alert("Update Failed");
})
.always((): void => {
this.displayTasks();
});
}

// ...
}

The first and most obvious change when transforming plain JavaScript to TypeScript are explicit types. While they are not required, they make it clear which type of data is expected
where. Any deviation from the specified contract is immediately caught by TypeScript, helping you find possible issues as soon as possible during the development process. This is
particularly useful when working with AJAX responses and their data.
Another change that you might have noticed already is the TypeScript string interpolation. Using string interpolation simplifies dynamic string composition and increases the
readability of your code.
Compare plain JavaScript:

var restQuery = "/_api/Web/Lists/GetByTitle('" + TASK_LIST + "')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '" + startDate + "' and DueDate le '" + endDate + "')or(StartDate ge '" + startDate + "' and StartDate le '" + endDate + "'))";

to:

const restQuery: string = `/_api/Web/Lists/GetByTitle('${escape(this.properties.listName)}')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '${startDate}' and DueDate le '${endDate}')or(StartDate ge '${startDate}' and StartDate le '${endDate}'))`;

The additional benefit of using TypeScript string interpolation is that you don't need to escape quotes, which also simplifies composing REST queries.
3. To confirm that everything is working as expected, execute the following in the command line:

gulp serve --nobrowser

4. Go to the hosted Workbench and add the web part to the canvas. Although visually nothing has changed, the new code base uses TypeScript and its type definitions to help you
maintain the solution.

Replace jQuery AJAX calls with SharePoint Framework API


At this moment, the solution uses jQuery AJAX calls to communicate with the SharePoint REST API. For regular GET requests, the jQuery AJAX API is just as convenient as using the
SharePoint Framework SPHttpClient. The real difference is when performing POST requests such as the one for updating the event:
$.ajax({
url: this.context.pageContext.web.absoluteUrl + '/_api/contextinfo',
type: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
})
.then((data: { FormDigestValue: string }, textStatus: string, jqXHR: JQueryXHR): JQueryXHR => {
return $.ajax({
url: `${this.context.pageContext.web.absoluteUrl}\
/_api/Web/Lists/getByTitle('${escape(this.properties.listName)}')/Items(${id})`,
type: 'POST',
data: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": data.FormDigestValue,
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
});
})
.done((data: {}, textStatus: string, jqXHR: JQueryXHR): void => {
alert("Update Successful");
})
// ...

Because you want to update a list item, you need to provide SharePoint with a valid request digest token. While it's available on classic pages, it's valid for 3 minutes, so it's always the safest
to retrieve a valid token yourself before performing an update operation. After you obtain the request digest, you have to add it to request headers of the update request. If you don't, the
request fails.
SharePoint Framework SPHttpClient simplifies communicating with SharePoint because it automatically detects if the request is a POST request and needs a valid request digest. If it does,
the SPHttpClient automatically retrieves it from SharePoint and adds it to the request. By comparison, the same request issued using the SPHttpClient would look like this:

this.context.spHttpClient.post(`${this.context.pageContext.web.absoluteUrl}\
/_api/Web/Lists/getByTitle('${escape(this.properties.listName)}')/Items(${id})`, SPHttpClient.configurations.v1, {
body: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
})
.then((response: SPHttpClientResponse): void => {
// ...
});

1. To replace the original jQuery AJAX calls with the SharePoint Framework SPHttpClient API, in the code editor open the ./src/webparts/tasksCalendar/TasksCalendarWebPart.ts
file. To the list of imports add:

import { SPHttpClient, SPHttpClientResponse } from '@microsoft/sp-http';

2. In the TasksCalendarWebPart class, replace the displayTasks and updateTask methods with the following code:

export default class TasksCalendarWebPart extends BaseClientSideWebPart<ITasksCalendarWebPartProps> {


// ...

private displayTasks(): void {


$('#calendar').fullCalendar('destroy');
$('#calendar').fullCalendar({
weekends: false,
header: {
left: 'prev,next today',
center: 'title',
right: 'month,basicWeek,basicDay'
},
displayEventTime: false,
// open up the display form when a user clicks on an event
eventClick: (calEvent: FC.EventObject, jsEvent: MouseEvent, view: FC.ViewObject): void => {
(window as any).location = `${this.context.pageContext.web.absoluteUrl}\
/Lists/${escape(this.properties.listName)}/DispForm.aspx?ID=${calEvent.id}`;
},
editable: true,
timezone: "UTC",
droppable: true, // this allows things to be dropped onto the calendar
// update the end date when a user drags and drops an event
eventDrop: (event: FC.EventObject, delta: moment.Duration, revertFunc: Function): void => {
this.updateTask(event.id, <moment.Moment>event.start, <moment.Moment>event.end);
},
// put the events on the calendar
events: (start: moment.Moment, end: moment.Moment, timezone: string, callback: Function): void => {
const startDate: string = start.format('YYYY-MM-DD');
const endDate: string = end.format('YYYY-MM-DD');

const restQuery: string = `/_api/Web/Lists/GetByTitle('${escape(this.properties.listName)}')/items?$select=ID,Title,\


Status,StartDate,DueDate,AssignedTo/Title&$expand=AssignedTo&\
$filter=((DueDate ge '${startDate}' and DueDate le '${endDate}')or(StartDate ge '${startDate}' and StartDate le '${endDate}'))`;

this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + restQuery, SPHttpClient.configurations.v1, {


headers: {
headers: {
'Accept': "application/json;odata.metadata=none"
}
})
.then((response: SPHttpClientResponse): Promise<{ value: ITask[] }> => {
return response.json();
})
.then((data: { value: ITask[] }): void => {
let personColors: { [person: string]: string; } = {};
let colorNo: number = 0;

const events: FC.EventObject[] = data.value.map((task: ITask): FC.EventObject => {


const assignedTo: string = task.AssignedTo.map((person: { Title: string }): string => {
return person.Title;
}).join(', ');

let color: string = personColors[assignedTo];


if (!color) {
color = this.colors[colorNo++];
personColors[assignedTo] = color;
}
if (colorNo >= this.colors.length) {
colorNo = 0;
}

return {
title: `${task.Title} - ${assignedTo}`,
id: task.ID,
// specify the background color and border color can also create a class and use className paramter
color: color,
start: moment.utc(task.StartDate).add("1", "days"),
// add one day to end date so that calendar properly shows event ending on that day
end: moment.utc(task.DueDate).add("1", "days")
};
});

callback(events);
});
}
});
}

private updateTask(id: number, startDate: moment.Moment, dueDate: moment.Moment): void {


// subtract the previously added day to the date to store correct date
const sDate: string = moment.utc(startDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
startDate.format("hh:mm") + ":00Z";
if (!dueDate) {
dueDate = startDate;
}
const dDate: string = moment.utc(dueDate).add("-1", "days").format('YYYY-MM-DD') + "T" +
dueDate.format("hh:mm") + ":00Z";

this.context.spHttpClient.post(`${this.context.pageContext.web.absoluteUrl}\
/_api/Web/Lists/getByTitle('${escape(this.properties.listName)}')/Items(${id})`, SPHttpClient.configurations.v1, {
body: JSON.stringify({
StartDate: sDate,
DueDate: dDate,
}),
headers: {
Accept: "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"IF-MATCH": "*",
"X-Http-Method": "PATCH"
}
})
.then((response: SPHttpClientResponse): void => {
if (response.ok) {
alert("Update Successful");
}
else {
alert("Update Failed");
}

this.displayTasks();
});
}

// ...
}

IM P O R T A N T

If you're suppressing metadata in the responses of the SharePoint REST API, when using the SharePoint Framework SPHttpClient you have to ensure that you're using
application/json;odata.metadata=none and not application/json;odata=nometadata as the value of the Accept header. SPHttpClient uses OData 4.0 and requires the first value. If you
use the latter instead, the request fails with a 406 Not Acceptable response.
3. To confirm that everything is working as expected, execute the following in the command line:

gulp serve --nobrowser

4. Go to the hosted Workbench and add the web part to the canvas. Although there are still no visual changes, the new code uses the SharePoint Framework SPHttpClient, which
simplifies your code and maintains your solution.
Migrate AngularJS applications to SharePoint Framework
3/26/2018 • 19 minutes to read Edit Online

Many organizations have been using AngularJS for building SharePoint solutions in the past. This article shows how to migrate an existing AngularJS application styled using
ngOfficeUIFabric - AngularJS directives for Office UI Fabric, to a SharePoint Framework client-side web part. The sample application used for this tutorial manages To Do items stored in a
SharePoint list.

The source of the AngularJS application is available on GitHub at angular-migration/angular-todo.


The source of the AngularJS application migrated to SharePoint Framework is available on GitHub at samples/angular-todo.
NOTE

Before following the steps in this article, be sure to set up your development environment for building SharePoint Framework solutions.

Set up project
Before you start migrating your AngularJS application, create and set up a new SharePoint Framework project to host the AngularJS application.

Create new project


1. Create a new folder for your project:

md angular-todo

2. Go to the project folder:

cd angular-todo

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, define values as follows:


angular-todo as your solution name
Use the current folder for the location to place the files
To do as your web part name
Simple management of to do tasks as your web part description
No JavaScript web framework as the starting point to build the web part
5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. In this tutorial, you will use Visual Studio Code.

Add AngularJS and ngOfficeUIFabric


In this tutorial you load both AngularJS and ngOfficeUIFabric from CDN.
In the code editor, open the config/config.json file, and in the externals property, add the following lines:

"angular": {
"path": "https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.6/angular.min.js",
"globalName": "angular"
},
"ng-office-ui-fabric": "https://cdnjs.cloudflare.com/ajax/libs/ngOfficeUiFabric/0.12.3/ngOfficeUiFabric.js"

Add AngularJS typings for TypeScript


Because you are referencing AngularJS in your web part's code, you also need AngularJS typings for TypeScript. To install them run the following in the command line:
npm install @types/angular --save-dev

Migrate the AngularJS application as-is


Start with migrating the AngularJS application with only the minimal code changes. Later, you will upgrade the application's plain JavaScript code to TypeScript and improve its integration
with the client-side web part.

Create SharePoint list


In your SharePoint site, create a new list called Todo. In the list, add a new choice column called Status. As available choices enter:

Not started
In progress
Completed

Copy AngularJS application files to the web part project


1. In the web part project, in the src/webparts/toDo folder, create a new folder called app .
2. From the source application, copy the contents of the app folder to the newly created app folder in the web part project.
Load the AngularJS application in the client-side web part
1. In the code editor, open the ./src/webparts/toDo/ToDoWebPart.ts file. After the last import statement, add the following code:

import * as angular from 'angular';


import 'ng-office-ui-fabric';

2. Change the contents of the render method to:

export default class ToDoWebPart extends BaseClientSideWebPart<IToDoWebPartProps> {


// ...
public render(): void {
if (this.renderedOnce === false) {
require('./app/app.module');
require('./app/app.config');
require('./app/data.service');
require('./app/home.controller');

this.domElement.innerHTML = `
<div class="${styles.toDo}">
<div data-ng-controller="homeController as vm">
<div class="${styles.loading}" ng-show="vm.isLoading">
<uif-spinner>Loading...</uif-spinner>
</div>
<div class="entryform" ng-show="vm.isLoading === false">
<uif-textfield uif-label="New to do:" uif-underlined ng-model="vm.newItem" ng-keydown="vm.todoKeyDown($event)"></uif-textfield>
</div>
<uif-list class="items" ng-show="vm.isLoading === false" >
<uif-list-item ng-repeat="todo in vm.todoCollection" uif-item="todo" ng-class="{'done': todo.done}">
<uif-list-item-primary-text>{{todo.title}}</uif-list-item-primary-text>
<uif-list-item-actions>
<uif-list-item-action ng-click="vm.completeTodo(todo)" ng-show="todo.done === false">
<uif-icon uif-type="check"></uif-icon>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.undoTodo(todo)" ng-show="todo.done">
<uif-icon uif-type="reactivate"></uif-icon>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.deleteTodo(todo)">
<uif-icon uif-type="trash"></uif-icon>
</uif-list-item-action>
</uif-list-item-actions>
</uif-list-item>
</uif-list>
</div>
</div>`;

angular.bootstrap(this.domElement, ['todoapp']);
}
}
// ...
}

Update site path


In the code editor, open the ./src/webparts/toDo/app/app.config.js file. Change the value of the sharepointApi constant to the server-relative URL of the SharePoint site where you
created the Todo list, followed by /_api/ .

Add CSS styles


You also need to implement CSS styles that you are using the template. In the code editor, open the ToDoWebPart.module.scss file and replace its contents with:

.toDo {
.loading {
margin: 0 auto;
width: 6em;
}
}

Trust the development certificate


By default, the development certificate required to load SharePoint Workbench and its resources over HTTPS is not trusted, and causes the web browser to show a warning when navigating
to the SharePoint Workbench. In situations when you want to run SharePoint Workbench in the context of SharePoint, some web browsers prevent the Workbench from loading if the SSL
certificate isn't trusted. To avoid this issue, you should trust the development certificate provided with the SharePoint Framework.
In the command line, execute:

gulp trust-dev-cert

Preview web part in the hosted Workbench


1. In the command line, execute:

gulp serve --nobrowser

2. To the URL of your SharePoint site, add /_layouts/workbench.aspx , for example, https://contoso.sharepoint.com/_layouts/workbench.aspx , and navigate to it in the web browser.
If you followed all steps correctly, you should see the web part in the browser showing the form to add To Do items.
3. Add a few To Do items to verify that the web part is working as expected.

Fix web part styling


Although the web part is working correctly, it doesn't look the same as the AngularJS application you started with. This is caused because ngOfficeUIFabric uses an older version of Office UI
Fabric than the one available in the SharePoint Workbench. The easy fix would be to load the CSS styles used by ngOfficeUIFabric. The problem with that is that these styles would collide
with the Office UI Fabric styles used by the SharePoint Workbench, breaking its user interface. A better solution is to add the styles required by the specific components to the web part
styles.
1. In the code editor, open the ./src/webparts/toDo/ToDoWebPart.module.scss file. Change its contents to:

.toDo {
.loading {
margin: 0 auto;
width: 6em;
}

.done :global .ms-ListItem-primaryText {


text-decoration: line-through;
}

ul, li {
margin: 0;
padding: 0;
}

:global {
.ms-Spinner{position:relative;height:20px}.ms-Spinner.ms-Spinner--large{height:28px}.ms-Spinner.ms-Spinner--large .ms-Spinner-label{left:34px;top:6px}.ms-Spinner-circle{position:absolute;b
.ms-TextField{color:#333;font-family:Segoe UI Regular WestEuropean,Segoe UI,Tahoma,Arial,sans-serif;font-size:14px;font-weight:400;box-sizing:border-box;margin:0;padding:0;box-shadow:none;
.ms-Label{margin:0;padding:0;box-shadow:none;box-sizing:border-box;display:block;padding:5px 0}.ms-Label.is-required:after{content:' *';color:#a80000}.ms-Label.is-disabled{color:#a6a6a6}@m
.ms-ListItem{font-family:"Segoe UI WestEuropean","Segoe UI",-apple-system,BlinkMacSystemFont,Roboto,"Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;font-size:14px;font-weigh
}
}

2. In the ./src/webparts/toDo/ToDoWebPart.ts file, in the render method, change the application rendering template to use new Office UI Fabric icons.
export default class ToDoWebPart extends BaseClientSideWebPart<IToDoWebPartProps> {
// ...
public render(): void {
if (this.renderedOnce === false) {
require('./app/app.module');
require('./app/app.config');
require('./app/data.service');
require('./app/home.controller');

this.domElement.innerHTML = `
<div class="${styles.toDo}">
<div data-ng-controller="homeController as vm">
<div class="${styles.loading}" ng-show="vm.isLoading">
<uif-spinner>Loading...</uif-spinner>
</div>
<div id="entryform" ng-show="vm.isLoading === false">
<uif-textfield uif-label="New to do:" uif-underlined ng-model="vm.newItem" ng-keydown="vm.todoKeyDown($event)"></uif-textfield>
</div>
<uif-list id="items" ng-show="vm.isLoading === false" >
<uif-list-item ng-repeat="todo in vm.todoCollection" uif-item="todo" ng-class="{'${styles.done}': todo.done}">
<uif-list-item-primary-text>{{todo.title}}</uif-list-item-primary-text>
<uif-list-item-actions>
<uif-list-item-action ng-click="vm.completeTodo(todo)" ng-show="todo.done === false">
<i class="ms-Icon ms-Icon--CheckMark" aria-hidden="true"></i>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.undoTodo(todo)" ng-show="todo.done">
<i class="ms-Icon ms-Icon--RevToggleKey" aria-hidden="true"></i>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.deleteTodo(todo)">
<i class="ms-Icon ms-Icon--Delete" aria-hidden="true"></i>
</uif-list-item-action>
</uif-list-item-actions>
</uif-list-item>
</uif-list>
</div>
</div>`;

angular.bootstrap(this.domElement, ['todoapp']);
}
}
// ...
}

If you refresh the web part in the web browser, you see that it is now correctly styled.

Upgrade the AngularJS application to TypeScript


The original AngularJS application is written in plain JavaScript, which makes maintaining it error-prone. When building SharePoint Framework client-side web parts, you can use TypeScript
and benefit from its design-time type safety features. In the next section, you will migrate the plain JavaScript AngularJS code to TypeScript.

Upgrade application configuration


In your project, rename the ./src/webparts/toDo/app/app.config.js file to app.config.ts . Change its contents to:

import * as angular from 'angular';

export default function() {


const todoapp: ng.IModule = angular.module('todoapp');
todoapp.constant('sharepointApi', '/todo/_api/');
todoapp.constant('todoListName', 'Todo');
todoapp.constant('hideFinishedTasks', false);
}

Upgrade data service


In your project, rename the ./src/webparts/toDo/app/data.service.js file to DataService.ts . Change its contents to:

import * as angular from 'angular';


import * as angular from 'angular';

export interface ITodo {


id: number;
title: string;
done: boolean;
}

interface ITodoItem {
Id: number;
Title: string;
Status: string;
}

export interface IDataService {


getTodos: () => angular.IPromise<ITodo[]>;
addTodo: (todo: string) => angular.IPromise<{}>;
deleteTodo: (todo: ITodo) => angular.IPromise<{}>;
setTodoStatus: (todo: ITodo, done: boolean) => angular.IPromise<{}>;
}

export default class DataService implements IDataService {


public static $inject: string[] = ['$q', '$http', 'sharepointApi', 'todoListName', 'hideFinishedTasks'];

constructor(private $q: angular.IQService,


private $http: angular.IHttpService,
private sharepointApi: string,
private todoListName: string,
private hideFinishedTasks: boolean) {
}

public getTodos(): angular.IPromise<ITodo[]> {


const deferred: angular.IDeferred<ITodo[]> = this.$q.defer();

let url: string = `${this.sharepointApi}web/lists/getbytitle('${this.todoListName}')/items?$select=Id,Title,Status&$orderby=ID desc`;

if (this.hideFinishedTasks === true) {


url += "&$filter=Status ne 'Completed'";
}

this.$http({
url: url,
method: 'GET',
headers: {
'Accept': 'application/json;odata=nometadata'
}
}).then((result: angular.IHttpPromiseCallbackArg<{ value: ITodoItem[] }>): void => {
const todos: ITodo[] = [];
for (let i: number = 0; i < result.data.value.length; i++) {
const todo: ITodoItem = result.data.value[i];
todos.push({
id: todo.Id,
title: todo.Title,
done: todo.Status === 'Completed'
});
}
deferred.resolve(todos);
});

return deferred.promise;
}

public addTodo(todo: string): angular.IPromise<{}> {


const deferred: angular.IDeferred<{}> = this.$q.defer();

let listItemEntityTypeFullName: string = undefined;


this.getListItemEntityTypeFullName()
.then((entityTypeName: string): angular.IPromise<string> => {
listItemEntityTypeFullName = entityTypeName;
return this.getRequestDigest();
})
.then((requestDigest: string): void => {
const body: string = JSON.stringify({
'__metadata': { 'type': listItemEntityTypeFullName },
'Title': todo
});
this.$http({
url: `${this.sharepointApi}web/lists/getbytitle('${this.todoListName}')/items`,
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-type': 'application/json;odata=verbose',
'X-RequestDigest': requestDigest
},
data: body
}).then((result: angular.IHttpPromiseCallbackArg<{}>): void => {
deferred.resolve();
});
});

return deferred.promise;
}

public deleteTodo(todo: ITodo): angular.IPromise<{}> {


const deferred: angular.IDeferred<{}> = this.$q.defer();

this.getRequestDigest()
.then((requestDigest: string): void => {
this.$http({
url: `${this.sharepointApi}web/lists/getbytitle('${this.todoListName}')/items(${todo.id})`,
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata',
'X-RequestDigest': requestDigest,
'IF-MATCH': '*',
'X-HTTP-Method': 'DELETE'
'X-HTTP-Method': 'DELETE'
}
}).then((result: angular.IHttpPromiseCallbackArg<{}>): void => {
deferred.resolve();
});
});

return deferred.promise;
}

public setTodoStatus(todo: ITodo, done: boolean): angular.IPromise<{}> {


const deferred: angular.IDeferred<{}> = this.$q.defer();

let listItemEntityTypeFullName: string = undefined;


this.getListItemEntityTypeFullName()
.then((entityTypeName: string): angular.IPromise<string> => {
listItemEntityTypeFullName = entityTypeName;
return this.getRequestDigest();
})
.then((requestDigest: string): void => {
const body: string = JSON.stringify({
'__metadata': { 'type': listItemEntityTypeFullName },
'Status': done ? 'Completed' : 'Not started'
});
this.$http({
url: `${this.sharepointApi}web/lists/getbytitle('${this.todoListName}')/items(${todo.id})`,
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-type': 'application/json;odata=verbose',
'X-RequestDigest': requestDigest,
'IF-MATCH': '*',
'X-HTTP-Method': 'MERGE'
},
data: body
}).then((result: angular.IHttpPromiseCallbackArg<{}>): void => {
deferred.resolve();
});
});

return deferred.promise;
}

private getRequestDigest(): angular.IPromise<string> {


const deferred: angular.IDeferred<string> = this.$q.defer();

this.$http({
url: this.sharepointApi + 'contextinfo',
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
}).then((result: angular.IHttpPromiseCallbackArg<{ FormDigestValue: string }>): void => {
deferred.resolve(result.data.FormDigestValue);
}, (err: any): void => {
deferred.reject(err);
});

return deferred.promise;
}

private getListItemEntityTypeFullName(): angular.IPromise<string> {


const deferred: angular.IDeferred<string> = this.$q.defer();

this.$http({
url: `${this.sharepointApi}web/lists/getbytitle('${this.todoListName}')?$select=ListItemEntityTypeFullName`,
method: 'GET',
headers: {
'Accept': 'application/json;odata=nometadata'
}
}).then((result: angular.IHttpPromiseCallbackArg<{ ListItemEntityTypeFullName: string }>): void => {
deferred.resolve(result.data.ListItemEntityTypeFullName);
}, (err: any): void => {
deferred.reject(err);
});

return deferred.promise;
}
}

Upgrade home controller


In your project, rename the ./src/webparts/toDo/app/home.controller.js file to HomeController.ts . Change its contents to:
import * as angular from 'angular';
import { IDataService, ITodo } from './DataService';

export default class HomeController {


public isLoading: boolean = false;
public newItem: string = null;
public todoCollection: ITodo[] = [];

public static $inject: string[] = ['DataService', '$window'];

constructor(private dataService: IDataService, private $window: angular.IWindowService) {


this.loadTodos();
}

private loadTodos(): void {


this.isLoading = true;
this.dataService.getTodos()
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
})
.finally((): void => {
this.isLoading = false;
});
}

public todoKeyDown($event: KeyboardEvent): void {


if ($event.keyCode === 13 && this.newItem.length > 0) {
$event.preventDefault();

this.todoCollection.unshift({ id: -1, title: this.newItem, done: false });

this.dataService.addTodo(this.newItem)
.then((): void => {
this.newItem = null;
this.dataService.getTodos()
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}
}

public deleteTodo(todo: ITodo): void {


if (this.$window.confirm('Are you sure you want to delete this todo item?')) {
let index: number = -1;
for (let i: number = 0; i < this.todoCollection.length; i++) {
if (this.todoCollection[i].id === todo.id) {
index = i;
break;
}
}

if (index > -1) {


this.todoCollection.splice(index, 1);
}

this.dataService.deleteTodo(todo)
.then((): void => {
this.dataService.getTodos()
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}
}

public completeTodo(todo: ITodo): void {


todo.done = true;

this.dataService.setTodoStatus(todo, true)
.then((): void => {
this.dataService.getTodos()
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}

public undoTodo(todo: ITodo): void {


todo.done = false;

this.dataService.setTodoStatus(todo, false)
.then((): void => {
this.dataService.getTodos()
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}
}

Upgrade application module


In your project, rename the ./src/webparts/toDo/app/app.module.js file to app.module.ts . Change its contents to:
import * as angular from 'angular';
import config from './app.config';
import HomeController from './HomeController';
import DataService from './DataService';

import 'ng-office-ui-fabric';

const todoapp: angular.IModule = angular.module('todoapp', [


'officeuifabric.core',
'officeuifabric.components'
]);

config();

todoapp
.controller('HomeController', HomeController)
.service('DataService', DataService);

Update reference to AngularJS application in the web part


Now that the AngularJS application is built using TypeScript, and its different pieces reference each other, it's no longer necessary for the web part to reference all pieces of the application.
Instead, it only needs to load the main module, which in turn loads all other elements that build up the AngularJS application.
1. In the code editor, open the ./src/webparts/toDo/ToDoWebPart.ts file. Change the render method to:

export default class ToDoWebPart extends BaseClientSideWebPart<IToDoWebPartProps> {


// ...
public render(): void {
if (this.renderedOnce === false) {
require('./app/app.module');

this.domElement.innerHTML = `
<div class="${styles.toDo}">
<div data-ng-controller="HomeController as vm">
<div class="${styles.loading}" ng-show="vm.isLoading">
<uif-spinner>Loading...</uif-spinner>
</div>
<div id="entryform" ng-show="vm.isLoading === false">
<uif-textfield uif-label="New to do:" uif-underlined ng-model="vm.newItem" ng-keydown="vm.todoKeyDown($event)"></uif-textfield>
</div>
<uif-list id="items" ng-show="vm.isLoading === false" >
<uif-list-item ng-repeat="todo in vm.todoCollection" uif-item="todo" ng-class="{'${styles.done}': todo.done}">
<uif-list-item-primary-text>{{todo.title}}</uif-list-item-primary-text>
<uif-list-item-actions>
<uif-list-item-action ng-click="vm.completeTodo(todo)" ng-show="todo.done === false">
<i class="ms-Icon ms-Icon--CheckMark" aria-hidden="true"></i>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.undoTodo(todo)" ng-show="todo.done">
<i class="ms-Icon ms-Icon--RevToggleKey" aria-hidden="true"></i>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.deleteTodo(todo)">
<i class="ms-Icon ms-Icon--Delete" aria-hidden="true"></i>
</uif-list-item-action>
</uif-list-item-actions>
</uif-list-item>
</uif-list>
</div>
</div>`;

angular.bootstrap(this.domElement, ['todoapp']);
}
}
// ...
}

2. To verify that the upgrade to TypeScript has been successful, in the command line, run:

gulp serve --nobrowser

3. In the web browser, refresh the SharePoint Workbench, which should display your web part as previously.
Even though the way the web part works hasn't changed, your code is improved. In case of a future update, you can more easily verify the correctness and integrity of your code already
during development.

Improve integration of the AngularJS application with the SharePoint Framework


At this point the AngularJS application works correctly and is wrapped in a SharePoint Framework client-side web part. While users can add the web part to the page, they cannot configure
how the web part should work. All of the configuration is embedded in the AngularJS application's code. In this section, you will extend the web part to allow configuration of the name of
the list where the To Do items are stored and whether the web part should show finished tasks or not.

Define web part properties


1. In the code editor, open the ./src/webparts/toDo/ToDoWebPart.manifest.json file. Change the properties section to:

"properties": {
"todoListName": "Todo",
"hideFinishedTasks": false
}

2. In the ./src/webparts/toDo/ToDoWebPart.ts file, change the definition of the IToDoWebPartProps interface to:

export interface IToDoWebPartProps {


todoListName: string;
hideFinishedTasks: boolean;
}

3. In the ./src/webparts/toDo/ToDoWebPart.ts file, change the first import statement to:

import {
BaseClientSideWebPart,
IPropertyPaneSettings,
PropertyPaneTextField,
PropertyPaneToggle
} from '@microsoft/sp-webpart-base';

4. In the same file, change the getPropertyPaneConfiguration method to:

export default class ToDoWebPart extends BaseClientSideWebPart<IToDoWebPartProps> {


// ...
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('todoListName', {
label: strings.ListNameFieldLabel
}),
PropertyPaneToggle('hideFinishedTasks', {
label: strings.HideFinishedTasksFieldLabel
})
]
}
]
}
]
};
}
// ...
}

5. Add the missing resource strings by changing the ./src/webparts/toDo/loc/mystrings.d.ts file contents to:
declare interface IToDoWebPartStrings {
PropertyPaneDescription: string;
BasicGroupName: string;
ListNameFieldLabel: string;
HideFinishedTasksFieldLabel: string;
}

declare module 'ToDoWebPartStrings' {


const strings: IToDoWebPartStrings;
export = strings;
}

6. In the ./src/webparts/toDo/loc/en-us.js file, add translations for the newly added strings:

define([], function() {
return {
"PropertyPaneDescription": "Description",
"BasicGroupName": "Group Name",
"ListNameFieldLabel": "List name",
"HideFinishedTasksFieldLabel": "Hide finished tasks"
}
});

Pass web part properties values to the AngularJS application


At this moment users can configure how the web part should work, but the AngularJS application isn't using these values. In the following sections, you will extend the AngularJS application
to use the configuration values provided by users through the web part property pane. One way to do that is to broadcast an AngularJS event in the render method and subscribe to this
event in the controller used in the web part.

Delete AngularJS configuration file


In your project, delete the ./src/webparts/toDo/app/app.config.ts file. In the following steps you will update the application to get the configuration values from web part properties.

Remove reference to configuration


In the ./src/webparts/toDo/app/app.module.ts file, remove the reference to the AngularJS configuration by changing its contents to:

import * as angular from 'angular';


import HomeController from './HomeController';
import DataService from './DataService';

import 'ng-office-ui-fabric';

const todoapp: angular.IModule = angular.module('todoapp', [


'officeuifabric.core',
'officeuifabric.components'
]);

todoapp
.controller('HomeController', HomeController)
.service('DataService', DataService);

Update data service to accept configuration value in method parameters


Originally the data service retrieved its configuration from the constants defined in the app.config.ts file. To use the configuration values configured in the web part properties instead, the
specific methods must accept parameters.
In the code editor, open the ./src/webparts/toDo/app/DataService.ts file, and change its contents to:

import * as angular from 'angular';

export interface ITodo {


id: number;
title: string;
done: boolean;
}

interface ITodoItem {
Id: number;
Title: string;
Status: string;
}

export interface IDataService {


getTodos: (sharePointApi: string, todoListName: string, hideFinishedTasks: boolean) => angular.IPromise<ITodo[]>;
addTodo: (todo: string, sharePointApi: string, todoListName: string) => angular.IPromise<{}>;
deleteTodo: (todo: ITodo, sharePointApi: string, todoListName: string) => angular.IPromise<{}>;
setTodoStatus: (todo: ITodo, done: boolean, sharePointApi: string, todoListName: string) => angular.IPromise<{}>;
}

export default class DataService implements IDataService {


public static $inject: string[] = ['$q', '$http'];

constructor(private $q: angular.IQService, private $http: angular.IHttpService) {


}

public getTodos(sharePointApi: string, todoListName: string, hideFinishedTasks: boolean): angular.IPromise<ITodo[]> {


const deferred: angular.IDeferred<ITodo[]> = this.$q.defer();

let url: string = `${sharePointApi}web/lists/getbytitle('${todoListName}')/items?$select=Id,Title,Status&$orderby=ID desc`;

if (hideFinishedTasks === true) {


url += "&$filter=Status ne 'Completed'";
}

this.$http({
url: url,
method: 'GET',
headers: {
'Accept': 'application/json;odata=nometadata'
}
}).then((result: angular.IHttpPromiseCallbackArg<{ value: ITodoItem[] }>): void => {
const todos: ITodo[] = [];
for (let i: number = 0; i < result.data.value.length; i++) {
const todo: ITodoItem = result.data.value[i];
todos.push({
id: todo.Id,
title: todo.Title,
done: todo.Status === 'Completed'
});
}
deferred.resolve(todos);
});

return deferred.promise;
}

public addTodo(todo: string, sharePointApi: string, todoListName: string): angular.IPromise<{}> {


const deferred: angular.IDeferred<{}> = this.$q.defer();

let listItemEntityTypeFullName: string = undefined;


this.getListItemEntityTypeFullName(sharePointApi, todoListName)
.then((entityTypeName: string): angular.IPromise<string> => {
listItemEntityTypeFullName = entityTypeName;
return this.getRequestDigest(sharePointApi);
})
.then((requestDigest: string): void => {
const body: string = JSON.stringify({
'__metadata': { 'type': listItemEntityTypeFullName },
'Title': todo
});
this.$http({
url: `${sharePointApi}web/lists/getbytitle('${todoListName}')/items`,
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-type': 'application/json;odata=verbose',
'X-RequestDigest': requestDigest
},
data: body
}).then((result: angular.IHttpPromiseCallbackArg<{}>): void => {
deferred.resolve();
});
});

return deferred.promise;
}

public deleteTodo(todo: ITodo, sharePointApi: string, todoListName: string): angular.IPromise<{}> {


const deferred: angular.IDeferred<{}> = this.$q.defer();

this.getRequestDigest(sharePointApi)
.then((requestDigest: string): void => {
this.$http({
url: `${sharePointApi}web/lists/getbytitle('${todoListName}')/items(${todo.id})`,
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata',
'X-RequestDigest': requestDigest,
'IF-MATCH': '*',
'X-HTTP-Method': 'DELETE'
}
}).then((result: angular.IHttpPromiseCallbackArg<{}>): void => {
deferred.resolve();
});
});

return deferred.promise;
}

public setTodoStatus(todo: ITodo, done: boolean, sharePointApi: string, todoListName: string): angular.IPromise<{}> {
const deferred: angular.IDeferred<{}> = this.$q.defer();

let listItemEntityTypeFullName: string = undefined;


this.getListItemEntityTypeFullName(sharePointApi, todoListName)
.then((entityTypeName: string): angular.IPromise<string> => {
listItemEntityTypeFullName = entityTypeName;
return this.getRequestDigest(sharePointApi);
})
.then((requestDigest: string): void => {
const body: string = JSON.stringify({
'__metadata': { 'type': listItemEntityTypeFullName },
'Status': done ? 'Completed' : 'Not started'
});
this.$http({
url: `${sharePointApi}web/lists/getbytitle('${todoListName}')/items(${todo.id})`,
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata',
'Content-type': 'application/json;odata=verbose',
'X-RequestDigest': requestDigest,
'IF-MATCH': '*',
'X-HTTP-Method': 'MERGE'
},
data: body
}).then((result: angular.IHttpPromiseCallbackArg<{}>): void => {
deferred.resolve();
});
});

return deferred.promise;
}

private getRequestDigest(sharePointApi: string): angular.IPromise<string> {


const deferred: angular.IDeferred<string> = this.$q.defer();
const deferred: angular.IDeferred<string> = this.$q.defer();

this.$http({
url: sharePointApi + 'contextinfo',
method: 'POST',
headers: {
'Accept': 'application/json;odata=nometadata'
}
}).then((result: angular.IHttpPromiseCallbackArg<{ FormDigestValue: string }>): void => {
deferred.resolve(result.data.FormDigestValue);
}, (err: any): void => {
deferred.reject(err);
});

return deferred.promise;
}

private getListItemEntityTypeFullName(sharePointApi: string, todoListName: string): angular.IPromise<string> {


const deferred: angular.IDeferred<string> = this.$q.defer();

this.$http({
url: `${sharePointApi}web/lists/getbytitle('${todoListName}')?$select=ListItemEntityTypeFullName`,
method: 'GET',
headers: {
'Accept': 'application/json;odata=nometadata'
}
}).then((result: angular.IHttpPromiseCallbackArg<{ ListItemEntityTypeFullName: string }>): void => {
deferred.resolve(result.data.ListItemEntityTypeFullName);
}, (err: any): void => {
deferred.reject(err);
});

return deferred.promise;
}
}

Broadcast properties change event


1. In the ./src/webparts/toDo/ToDoWebPart.ts file, to the ToDoWebPart class, add a new property called $injector :

export default class ToDoWebPart extends BaseClientSideWebPart<IToDoWebPartProps> {


private $injector: angular.auto.IInjectorService;
// ...
}

2. In the same file, update the render method to:

export default class ToDoWebPart extends BaseClientSideWebPart<IToDoWebPartProps> {


// ...
public render(): void {
if (this.renderedOnce === false) {
require('./app/app.module');

this.domElement.innerHTML = `
<div class="${styles.toDo}">
<div data-ng-controller="HomeController as vm">
<div class="${styles.configurationNeeded}" ng-show="vm.configurationNeeded">
Please configure the web part
</div>
<div ng-show="vm.configurationNeeded === false">
<div id="loading" ng-show="vm.isLoading">
<uif-spinner>Loading...</uif-spinner>
</div>
<div id="entryform" ng-show="vm.isLoading === false">
<uif-textfield uif-label="New to do:" uif-underlined ng-model="vm.newItem" ng-keydown="vm.todoKeyDown($event)"></uif-textfield>
</div>
<uif-list id="items" ng-show="vm.isLoading === false" >
<uif-list-item ng-repeat="todo in vm.todoCollection" uif-item="todo" ng-class="{'${styles.done}': todo.done}">
<uif-list-item-primary-text>{{todo.title}}</uif-list-item-primary-text>
<uif-list-item-actions>
<uif-list-item-action ng-click="vm.completeTodo(todo)" ng-show="todo.done === false">
<i class="ms-Icon ms-Icon--CheckMark" aria-hidden="true"></i>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.undoTodo(todo)" ng-show="todo.done">
<i class="ms-Icon ms-Icon--RevToggleKey" aria-hidden="true"></i>
</uif-list-item-action>
<uif-list-item-action ng-click="vm.deleteTodo(todo)">
<i class="ms-Icon ms-Icon--Delete" aria-hidden="true"></i>
</uif-list-item-action>
</uif-list-item-actions>
</uif-list-item>
</uif-list>
</div>
</div>
</div>`;

this.$injector = angular.bootstrap(this.domElement, ['todoapp']);


}

this.$injector.get('$rootScope').$broadcast('configurationChanged', {
sharePointApi: this.context.pageContext.web.absoluteUrl + '/_api/',
todoListName: this.properties.todoListName,
hideFinishedTasks: this.properties.hideFinishedTasks
});
}
// ...
}

3. In the ./src/webparts/toDo/ToDoWebPart.module.scss file, add the missing styles for the .configurationNeeded class:
.toDo {
/* ... */
.configurationNeeded {
margin: 0 auto;
width: 100%;
text-align: center;
}
/* ... */
}

Subscribe to the properties changed event


1. In the code editor, open the ./src/webparts/toDo/app/HomeController.ts file. In the HomeController class, add the following properties:

export default class HomeController {


// ...
private sharePointApi: string = undefined;
private todoListName: string = undefined;
private hideFinishedTasks: boolean = false;
private configurationNeeded: boolean = true;
// ...
}

2. Extend the constructor of the HomeController class with injecting the root scope service, and change its contents to:

export default class HomeController {


// ...
public static $inject: string[] = ['DataService', '$window', '$rootScope'];

constructor(private dataService: IDataService,


private $window: angular.IWindowService,
$rootScope: angular.IRootScopeService) {
const vm: HomeController = this;
this.init(undefined, undefined);

$rootScope.$on('configurationChanged',
(event: angular.IAngularEvent,
args: {
sharePointApi: string;
todoListName: string;
hideFinishedTasks: boolean;
}): void => {
vm.init(args.sharePointApi, args.todoListName, args.hideFinishedTasks);
});
}

// ...
}

3. To the HomeController class, add the init method:

export default class HomeController {


// ...
private init(sharePointApi: string, todoListName: string, hideFinishedTasks?: boolean): void {
if (sharePointApi !== undefined && sharePointApi.length > 0 &&
todoListName !== undefined && todoListName.length > 0) {
this.sharePointApi = sharePointApi;
this.todoListName = todoListName;
this.hideFinishedTasks = hideFinishedTasks;
this.loadTodos();
this.configurationNeeded = false;
}
else {
this.configurationNeeded = true;
}
}
// ...
}

4. Update all remaining methods in the HomeController class to use the configuration values from the class properties:
export default class HomeController {
// ...
private loadTodos(): void {
this.isLoading = true;
this.dataService.getTodos(this.sharePointApi, this.todoListName, this.hideFinishedTasks)
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
})
.finally((): void => {
this.isLoading = false;
});
}

public todoKeyDown($event: KeyboardEvent): void {


if ($event.keyCode === 13 && this.newItem.length > 0) {
$event.preventDefault();

this.todoCollection.unshift({ id: -1, title: this.newItem, done: false });

this.dataService.addTodo(this.newItem, this.sharePointApi, this.todoListName)


.then((): void => {
this.newItem = null;
this.dataService.getTodos(this.sharePointApi, this.todoListName, this.hideFinishedTasks)
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}
}

public deleteTodo(todo: ITodo): void {


if (this.$window.confirm('Are you sure you want to delete this todo item?')) {
let index: number = -1;
for (let i: number = 0; i < this.todoCollection.length; i++) {
if (this.todoCollection[i].id === todo.id) {
index = i;
break;
}
}

if (index > -1) {


this.todoCollection.splice(index, 1);
}

this.dataService.deleteTodo(todo, this.sharePointApi, this.todoListName)


.then((): void => {
this.dataService.getTodos(this.sharePointApi, this.todoListName, this.hideFinishedTasks)
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}
}

public completeTodo(todo: ITodo): void {


todo.done = true;

this.dataService.setTodoStatus(todo, true, this.sharePointApi, this.todoListName)


.then((): void => {
this.dataService.getTodos(this.sharePointApi, this.todoListName, this.hideFinishedTasks)
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}

public undoTodo(todo: ITodo): void {


todo.done = false;

this.dataService.setTodoStatus(todo, false, this.sharePointApi, this.todoListName)


.then((): void => {
this.dataService.getTodos(this.sharePointApi, this.todoListName, this.hideFinishedTasks)
.then((todos: ITodo[]): void => {
this.todoCollection = todos;
});
});
}
}

5. Verify that the web part is working correctly by executing the following in the command line:

gulp serve --nobrowser

6. In your web browser, go to the SharePoint Workbench and add the web part to canvas. If you toggle the Hide finished tasks option, you should see completed tasks being displayed
or hidden accordingly.
Work with __REQUESTDIGEST
3/26/2018 • 3 minutes to read Edit Online

When executing non-GET REST requests to the SharePoint API, you must add a valid request digest to your request. This digest proves validity of your request to SharePoint. Because this
token is valid only for a limited period of time, you have to ensure that the token you have is valid before adding it to your request or the request fails.
In classic pages, SharePoint includes a request digest token on the page in a hidden field named __REQUESTDIGEST. One of the most common approaches to work with the request digest
is to obtain it from that field and add it to the request, for example:

var digest = $('#__REQUESTDIGEST').val();


$.ajax({
url: '/_api/web/...'
method: "POST",
headers: {
"Accept": "application/json; odata=nometadata",
"X-RequestDigest": digest
},
success: function (data) {
// ...
},
error: function (data, errorCode, errorMessage) {
// ...
}
});

Such a request would work initially, but if the user has the page open for a longer period of time, the request digest on the page expires and the request fails with a 403 FORBIDDEN result.
By default, a request digest token is valid for 30 minutes, so before using it, you have to ensure that it's still valid. In the past you had to do this manually, by comparing the timestamp from
the request digest with the current time.
SharePoint Framework simplifies this process by offering you two ways of ensuring that your request has a valid request digest token.

Use the SPHttpClient to communicate with the SharePoint REST API


The recommended way to communicate with the SharePoint REST API is to use the SPHttpClient provided with the SharePoint Framework. This class wraps issuing REST requests to the
SharePoint REST API with convenient logic that simplifies your code.
For example, whenever you issue a non-GET request using the SPHttpClient, it automatically obtains a valid request digest and adds it to the request. This significantly simplifies your
solution because you don't need to build code to manage request digest tokens and ensure their validity.
If you're building new customizations on the SharePoint Framework, you should always use the SPHttpClient to communicate with the SharePoint REST API.
Sometimes, however, you might not be able to use the SPHttpClient. This can be the case, for example, when you're migrating an existing customization to the SharePoint Framework and
want to keep as much of the original code as possible, or you're building a customization by using a library such as Angular(JS) that has its own services for issuing web requests. In such
cases you can obtain a valid request digest token from the DigestCache.

Retrieve a valid request digest by using the DigestCache service


If you can't use the SPHttpClient for communicating with the SharePoint REST API, you can obtain a valid request digest token by using the DigestCache service provided with the
SharePoint Framework.
The benefit of using the DigestCache service over manually obtaining a valid request digest token is that the DigestCache automatically checks if the previously retrieved request digest is
still valid. If it's expired, the DigestCache service automatically requests a new request digest token from SharePoint and stores it from subsequent requests. Using the DigestCache simplifies
your code and makes your solution more robust.

To use the DigestCache service in your code


1. Import the DigestCache and IDigestCache types from the @microsoft/sp-http package:

// ...
import { IDigestCache, DigestCache } from '@microsoft/sp-http';

export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {


// ...
}

2. Whenever you need a valid request digest token, retrieve a reference to the DigestCache service, and call its fetchDigest method:

// ...
import { IDigestCache, DigestCache } from '@microsoft/sp-http';

export default class HelloWorldWebPart extends BaseClientSideWebPart<IHelloWorldWebPartProps> {


protected onInit(): Promise<void> {
return new Promise<void>((resolve: () => void, reject: (error: any) => void): void => {
const digestCache: IDigestCache = this.context.serviceScope.consume(DigestCache.serviceKey);
digestCache.fetchDigest(this.context.pageContext.web.serverRelativeUrl).then((digest: string): void => {
// use the digest here
resolve();
});
});
}

// ...
}

See also
SharePoint Framework Overview
Connect to SharePoint using the JavaScript Object Model (JSOM)
5/3/2018 • 14 minutes to read Edit Online

In the past, when building SharePoint customizations, you might have used the SharePoint JavaScript Object Model (JSOM) to communicate with SharePoint. This is no longer the
recommended path (see Considerations later in this article), but there are still valid use cases such as code migration.
To use SharePoint JSOM in your SharePoint Framework component, you must first reference it. In the past it was already available on the page for you to use. In the SharePoint Framework,
it has to be explicitly loaded.
There are two ways to reference SharePoint JSOM in the SharePoint Framework:
Declarative - through configuration
Imperative - through code
Each of these approaches has advantages and disadvantages, and it's important for you to understand each of them.
NOTE

Before following the steps in this article, be sure to set up your SharePoint Framework development environment.

Create a new project


1. From the console, create a new folder for your project:

md react-sharepointlists

2. Go to the project folder:

cd react-sharepointlists

3. In the project folder, run the SharePoint Framework Yeoman generator to scaffold a new SharePoint Framework project:

yo @microsoft/sharepoint

4. When prompted, enter the following values:


react-sharepointlists as your solution name.
Select Webpart as the client-side component type to be created.
Use the current folder for the location to place the files.
React as the starting framework to build the web part.
SharePoint lists as your web part name.
Shows names of lists in the current site as your web part description.

5. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

6. Open your project folder in your code editor. This article uses Visual Studio Code in the steps and screenshots, but you can use any editor you prefer.
7. To open the directory in Visual Studio Code, from the console enter:

code .

Reference JSOM declaratively


Register the SharePoint JSOM API as external scripts
When referencing JSOM declaratively, the first step is to register the SharePoint JSOM API as external scripts within your SharePoint Framework project.
1. In your code editor, open the ./config/config.json file, and add the following to the externals section:

{
// ...
"externals": {
"sp-init": {
"path": "https://contoso.sharepoint.com/_layouts/15/init.js",
"globalName": "$_global_init"
},
"microsoft-ajax": {
"path": "https://contoso.sharepoint.com/_layouts/15/MicrosoftAjax.js",
"globalName": "Sys",
"globalDependencies": [
"sp-init"
]
},
"sp-runtime": {
"path": "https://contoso.sharepoint.com/_layouts/15/SP.Runtime.js",
"globalName": "SP",
"globalDependencies": [
"microsoft-ajax"
]
},
"sharepoint": {
"path": "https://contoso.sharepoint.com/_layouts/15/SP.js",
"globalName": "SP",
"globalDependencies": [
"sp-runtime"
]
}
}
// ...
}

Each of the entries points to different script files that together allow you to use SharePoint JSOM in your SPFx component. All of these scripts are distributed as non-module scripts.
This is why each registration entry requires a URL, (specified using the path property) and the name used by the script (provided in the globalName property). To ensure that these
scripts load in the right order, the dependencies between these scripts are specified by using the globalDependencies property.
Additional scripts may need to be added depending on the JSOM functionality that you are using (e.g. sp.taxonomy.js).

Install TypeScript typings for SharePoint JSOM


The next step is to install and configure TypeScript typings for SharePoint JSOM, which allows you to benefit from TypeScript's type safety features when working with SharePoint JSOM.
1. From the console, execute the following command within your project directory:
npm install @types/microsoft-ajax @types/sharepoint --save-dev

SharePoint JSOM is not distributed as a module, so you cannot import it directly in your code. Instead, you need to register its TypeScript typings globally.
2. In the code editor, open the ./tsconfig.json file, and in the types property, right after the webpack-env entry, add references to microsoft-ajax and sharepoint:

{
"compilerOptions": {
// ...
"types": [
"es6-promise",
"es6-collections",
"webpack-env",
"microsoft-ajax",
"sharepoint"
]
}
}

Reference SharePoint JSOM scripts in a React component


To load the SharePoint JSOM scripts in your SPFx component, you have to reference them in the component's code. In this example, you add the references in a React component where
JSOM is used to communicate with SharePoint.
In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file. After the last import statement, add the following code:

require('sp-init');
require('microsoft-ajax');
require('sp-runtime');
require('sharepoint');

These names correspond to the external references that you added previously, so SharePoint Framework loads these scripts from the specified URLs.
To demonstrate using SharePoint JSOM for communicating with SharePoint, you need to retrieve and render the titles of all SharePoint lists located in the current site.

Add siteUrl to the React component's properties


To connect to SharePoint, the React component must know the URL of the current site. That URL is available in the parent web part and can be passed into the component through its
properties.
1. In the code editor, open the ./src/webparts/sharePointLists/components/ISharePointListsProps.ts file, and add the siteUrl property to the ISharePointListsProps interface:

export interface ISharePointListsProps {


description: string;
siteUrl: string;
}

2. To pass the URL of the current site into the component, open the ./src/webparts/sharePointLists/SharePointListsWebPart.ts file in the code editor, and change the render
method to:

export default class SharePointListsWebPart extends BaseClientSideWebPart<ISharePointListsWebPartProps> {


public render(): void {
const element: React.ReactElement<ISharePointListsProps > = React.createElement(
SharePointLists,
{
description: this.properties.description,
siteUrl: this.context.pageContext.web.absoluteUrl
}
);

ReactDom.render(element, this.domElement);
}

// ...
}

Define the React component's state


The React component loads data from SharePoint and renders it to the user. The current state of the React component is modeled by using a state interface that we add.
In the code editor, in the ./src/webparts/sharePointLists/components folder, create a new file named ISharePointListsState.ts and paste in the following contents:

export interface ISharePointListsState {


listTitles: string[];
loadingLists: boolean;
error: string;
}

Add state to the React component


Having defined the interface describing the shape of the component's state, the next step is to have the React component use that state interface.
1. In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file. Under the existing import statements, add:

import { ISharePointListsState } from './ISharePointListsState';

2. Change the signature of the SharePointLists class to:

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


// ...
}
3. In the SharePointLists class, add a constructor with the default state value:

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


constructor(props?: ISharePointListsProps, context?: any) {
super();

this.state = {
listTitles: [],
loadingLists: false,
error: null
};
}

// ...
}

Load information about SharePoint lists from the current site using JSOM
The sample client-side web part used in this article loads information about SharePoint lists in the current site after selecting a button.

1. In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file. In the SharePointLists class, add a new method named getListsTitles :

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


constructor(props?: ISharePointListsProps, context?: any) {
super();

this.state = {
listTitles: [],
loadingLists: false,
error: null
};

this.getListsTitles = this.getListsTitles.bind(this);
}

// ...

private getListsTitles(): void {


}
}

2. To ensure the correct scoping of the method, we bind it to the web part in the constructor. In the getListsTitles method, use SharePoint JSOM to load the titles of SharePoint lists in
the current site:
export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {
// ...
private getListsTitles(): void {
this.setState({
loadingLists: true,
listTitles: [],
error: null
});

const context: SP.ClientContext = new SP.ClientContext(this.props.siteUrl);


const lists: SP.ListCollection = context.get_web().get_lists();
context.load(lists, 'Include(Title)');
context.executeQueryAsync((sender: any, args: SP.ClientRequestSucceededEventArgs): void => {
const listEnumerator: IEnumerator<SP.List> = lists.getEnumerator();

const titles: string[] = [];


while (listEnumerator.moveNext()) {
const list: SP.List = listEnumerator.get_current();
titles.push(list.get_title());
}

this.setState((prevState: ISharePointListsState, props: ISharePointListsProps): ISharePointListsState => {


prevState.listTitles = titles;
prevState.loadingLists = false;
return prevState;
});
}, (sender: any, args: SP.ClientRequestFailedEventArgs): void => {
this.setState({
loadingLists: false,
listTitles: [],
error: args.get_message()
});
});
}
}

We start by resetting the component's state to communicate to the user that the component is loading information from SharePoint. Using the URL of the current site passed to the
component through its properties, we instantiate a new SharePoint context. Using SharePoint JSOM, we load lists from the current site. To optimize the request for performance, we specify
that only the Title property should be loaded.
Next, we execute the query by calling the executeQueryAsync method and passing two callback functions. After the query is completed, we enumerate through the collection of retrieved lists,
store their titles in an array, and update the component's state.

Render the titles of SharePoint lists in the current site


Having loaded the titles of SharePoint lists in the current site, the final step is to render them in the component.
1. In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file, and update the render method:

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


// ...
public render(): React.ReactElement<ISharePointListsProps> {
const titles: JSX.Element[] = this.state.listTitles.map((listTitle: string, index: number, listTitles: string[]): JSX.Element => {
return <li key={index}>{listTitle}</li>;
});

return (
<div className={styles.sharePointLists}>
<div className={styles.container}>
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<span className="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
<p className="ms-font-l ms-fontColor-white">Customize SharePoint experiences using web parts.</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.description)}</p>
<a className={styles.button} onClick={this.getListsTitles} role="button">
<span className={styles.label}>Get lists titles</span>
</a><br />
{this.state.loadingLists &&
<span>Loading lists...</span>}
{this.state.error &&
<span>An error has occurred while loading lists: {this.state.error}</span>}
{this.state.error === null && titles &&
<ul>
{titles}
</ul>}
</div>
</div>
</div>
</div>
);
}
// ...
}

2. At this point, you should be able to add your web part to the page and see the titles of SharePoint lists in the current site. To verify that the project is working correctly, run the
following command from the console:

gulp serve --nobrowser

3. As you are using SharePoint JSOM to communicate with SharePoint, you have to test the web part by using the hosted version of the SharePoint Workbench (which is why the
--nobrowser parameter is specified to prevent the automatic loading of the local Workbench).
Referencing SharePoint JSOM scripts declaratively as external scripts is convenient and allows you to keep your code clean. One disadvantage, however, is that it requires specifying absolute
URLs to the location from which SharePoint JSOM scripts should be loaded. If you're using separate SharePoint tenants for development, testing, and production, it requires some additional
work to change these URLs for the different environments accordingly. In such cases, you may consider referencing JSOM imperatively by using the SPComponentLoader to load the scripts
in the SPFx component's code.

Reference JSOM imperatively


Another way to load JavaScript libraries in SharePoint Framework projects is to use the SPComponentLoader , a utility class provided with the SharePoint Framework designed to help you load
scripts and other resources in your components. One benefit of using the SPComponentLoader over loading scripts declaratively is that it allows you to use server-relative URLs, which is more
convenient when using different SharePoint tenants for the different stages of your development process.
For this portion of the tutorial, we'll be adjusting the code we created previously in the Declarative section.

Declarative reference cleanup


If you followed the steps in the declarative reference sections earlier, you need to remove those references.
1. Remove the existing external script references. In the code editor, open the ./config/config.json file, and from the externals property, remove all entries:

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/config.2.0.schema.json",
"version": "2.0",
"bundles": {
"share-point-lists-web-part": {
"components": [
{
"entrypoint": "./lib/webparts/sharePointLists/SharePointListsWebPart.js",
"manifest": "./src/webparts/sharePointLists/SharePointListsWebPart.manifest.json"
}
]
}
},
"externals": {},
"localizedResources": {
"SharePointListsWebPartStrings": "lib/webparts/sharePointLists/loc/{locale}.js"
}
}

With the SharePoint JSOM scripts no longer being registered as external scripts, you cannot reference them directly in your code.
2. In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file, and remove the require statements pointing to the different SharePoint JSOM
scripts.

Wait to load data until the JSOM scripts are loaded


The primary functionality of the client-side web part that we are building in this tutorial depends on SharePoint JSOM. Depending on a number of factors, loading these scripts could take a
few moments. When building SPFx components that utilize JSOM, you should take that into account. When added to the page, the web part should communicate to the user that it's loading
its prerequisites and should make it clear when it's ready to be used.
To support this, extend the React component's state with an additional property to track the status of loading the JSOM scripts.
1. In the code editor, open the ./src/webparts/sharePointLists/components/ISharePointListsState.ts file, and paste the following code:
export interface ISharePointListsState {
listTitles: string[];
loadingLists: boolean;
error: string;
loadingScripts: boolean;
}

2. Add the newly added property to the state definitions in the React component. In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file.
Update the constructor to the following code:

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


constructor(props?: ISharePointListsProps, context?: any) {
super();

this.state = {
listTitles: [],
loadingLists: false,
error: null,
loadingScripts: true
};

this.getListsTitles = this.getListsTitles.bind(this);
}
// ...
}

3. In the same file, update the getListsTitles method to the following code:

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


// ...
private getListsTitles(): void {
this.setState({
loadingLists: true,
listTitles: [],
error: null,
loadingScripts: false
});

const context: SP.ClientContext = new SP.ClientContext(this.props.siteUrl);


const lists: SP.ListCollection = context.get_web().get_lists();
context.load(lists, 'Include(Title)');
context.executeQueryAsync((sender: any, args: SP.ClientRequestSucceededEventArgs): void => {
const listEnumerator: IEnumerator<SP.List> = lists.getEnumerator();

const titles: string[] = [];


while (listEnumerator.moveNext()) {
const list: SP.List = listEnumerator.get_current();
titles.push(list.get_title());
}

this.setState((prevState: ISharePointListsState, props: ISharePointListsProps): ISharePointListsState => {


prevState.listTitles = titles;
prevState.loadingLists = false;
return prevState;
});
}, (sender: any, args: SP.ClientRequestFailedEventArgs): void => {
this.setState({
loadingLists: false,
listTitles: [],
error: args.get_message(),
loadingScripts: false
});
});
}
}

4. To communicate the status of loading the SharePoint JSOM scripts to the user, update the render method to the following code:
export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {
// ...
public render(): React.ReactElement<ISharePointListsProps> {
const titles: JSX.Element[] = this.state.listTitles.map((listTitle: string, index: number, listTitles: string[]): JSX.Element => {
return <li key={index}>{listTitle}</li>;
});

return (
<div className={styles.sharePointLists}>
<div className={styles.container}>
{this.state.loadingScripts &&
<div className="ms-Grid" style={{ color: "#666", backgroundColor: "#f4f4f4", padding: "80px 0", alignItems: "center", boxAlign: "center" }}>
<div className="ms-Grid-row" style={{ color: "#333" }}>
<div className="ms-Grid-col ms-u-hiddenSm ms-u-md3"></div>
<div className="ms-Grid-col ms-u-sm12 ms-u-md6" style={{ height: "100%", whiteSpace: "nowrap", textAlign: "center" }}>
<i className="ms-fontSize-su ms-Icon ms-Icon--CustomList" style={{ display: "inline-block", verticalAlign: "middle", whiteSpace: "normal" }}></i><span className="ms-fontWeigh
</div>
<div className="ms-Grid-col ms-u-hiddenSm ms-u-md3"></div>
</div>
<div className="ms-Grid-row" style={{ width: "65%", verticalAlign: "middle", margin: "0 auto", textAlign: "center" }}>
<span style={{ color: "#666", fontSize: "17px", display: "inline-block", margin: "24px 0", fontWeight: 100 }}>Loading SharePoint JSOM scripts...</span>
</div>
<div className="ms-Grid-row"></div>
</div>}
{this.state.loadingScripts === false &&
<div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
<div className="ms-Grid-col ms-u-lg10 ms-u-xl8 ms-u-xlPush2 ms-u-lgPush1">
<span className="ms-font-xl ms-fontColor-white">Welcome to SharePoint!</span>
<p className="ms-font-l ms-fontColor-white">Customize SharePoint experiences using web parts.</p>
<p className="ms-font-l ms-fontColor-white">{escape(this.props.description)}</p>
<a className={styles.button} onClick={this.getListsTitles} role="button">
<span className={styles.label}>Get lists titles</span>
</a><br />
{this.state.loadingLists &&
<span>Loading lists...</span>}
{this.state.error &&
<span>An error has occurred while loading lists: {this.state.error}</span>}
{this.state.error === null && titles &&
<ul>
{titles}
</ul>}
</div>
</div>
}
</div>
</div>
);
}
// ...
}

5. When the React component's state indicates that the SharePoint JSOM scripts are being loaded, it displays a placeholder. After the scripts have been loaded, the web part displays the
expected content with the button allowing users to load the information about SharePoint lists in the current site.

Load SharePoint JSOM scripts by using SPComponentLoader


SPFx components should load SharePoint JSOM scripts only once. In this example, given that the web part consists of a single React component, the best place to load SharePoint JSOM
scripts is inside the React component's componentDidMount method, which executes only once after the component has been instantiated.
1. In the code editor, open the ./src/webparts/sharePointLists/components/SharePointLists.tsx file. In the top section of the file, add an import statement referencing the
SPComponentLoader . In the SharePointLists class, add the componentDidMount method:

import { SPComponentLoader } from '@microsoft/sp-loader';

export default class SharePointLists extends React.Component<ISharePointListsProps, ISharePointListsState> {


// ...
public componentDidMount(): void {
SPComponentLoader.loadScript('/_layouts/15/init.js', {
globalExportsName: '$_global_init'
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/MicrosoftAjax.js', {
globalExportsName: 'Sys'
});
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/SP.Runtime.js', {
globalExportsName: 'SP'
});
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/SP.js', {
globalExportsName: 'SP'
});
})
.then((): void => {
this.setState((prevState: ISharePointListsState, props: ISharePointListsProps): ISharePointListsState => {
prevState.loadingScripts = false;
return prevState;
});
});
}
// ...
}

2. Using a series of chained promises, we load the different scripts that together enable using SharePoint JSOM in your SharePoint Framework component. Note that by using the
SPComponentLoader , you can use server-relative URLs that load the scripts from the current SharePoint tenant. After all scripts have been loaded, you update the React component's
state confirming that all prerequisites have been loaded and the web part is ready to use.
3. Confirm that the web part is working as expected by running the following command from the console:

gulp serve --nobrowser

Just as before, the web part should show the titles of SharePoint lists in the current site.

While using the SPComponentLoader requires some additional effort, it allows you to use server-relative URLs, which is beneficial in scenarios when you're using different tenants for
development, testing, and production.

Considerations
In the past, when building client-side customizations on SharePoint, you might have used SharePoint JSOM to communicate with SharePoint. However, the recommended approach is to use
the SharePoint REST API either directly or through the PnP JavaScript Core Library.
When SharePoint JSOM was introduced, it was the first step towards supporting client-side solutions on SharePoint. However, it is no longer being actively maintained and might not offer
access to all capabilities available through the REST API. Additionally, whether using the SharePoint REST API directly or through the PnP JavaScript Core Library, you can use promises
which significantly simplify writing asynchronous code (a common problem when utilizing JSOM).
Although there are still a limited number of cases where SharePoint JSOM provides access to data and methods not yet covered by the SharePoint REST API, where possible, the REST API
should be preferred.
If you have existing customizations using SharePoint JSOM and are considering migrating them to the SharePoint Framework, this article should provide you with the necessary
information about using SharePoint JSOM in SharePoint Framework solutions. Longer term, however, you should consider changing how you communicate with SharePoint to either using
the SharePoint REST API directly or through the PnP JavaScript Core Library.
Overview of SharePoint Framework Extensions
3/26/2018 • 2 minutes to read Edit Online

You can use SharePoint Framework (SPFx) Extensions to extend the SharePoint user experience. With SharePoint Framework Extensions, you can customize more facets of the SharePoint
experience, including notification areas, toolbars, and list data views. SharePoint Framework Extensions are available in all Office 365 subscriptions for production usage.
NOTE

You can get an Office 365 developer subscription when you join the Office 365 Developer Program. See the Office 365 Developer Program documentation for step-by-step instructions
about how to join the Office 365 Developer Program and sign up and configure your subscription.
SharePoint Framework Extensions enable you to extend the SharePoint user experience within modern pages and document libraries, while using the familiar SharePoint Framework tools
and libraries for client-side development. Specifically, the SharePoint Framework includes three new extension types:
Application Customizers. Adds scripts to the page, and accesses well-known HTML element placeholders and extends them with custom renderings.
Field Customizers. Provides modified views to data for fields within a list.
Command Sets. Extends the SharePoint command surfaces to add new actions, and provides client-side code that you can use to implement behaviors.
You can build extensions alongside common scripting frameworks, such as AngularJS and React, in addition to plain JavaScript projects. For example, you can use React along with
components from Office UI Fabric React to create experiences based on the same components used in Office 365.
NOTE

There is a known bug with list and library extension support in the classic experiences. These only work currently in context of modern team sites, also known as group associated team sites.
Work is being done to address this issue.

Get started
1. If you haven't installed the SharePoint Framework, follow the steps to Set up your development environment.
2. After you install the SharePoint Framework, run the following command to update your Yeoman templates with the latest version:

npm install -g @microsoft/generator-sharepoint

3. Next, you can Build your first SharePoint Framework Extension (Hello World part 1).

Stay up to date
To keep track of improvements to the SharePoint Framework, including updates to extensions, see the following:
@SharePoint and @OfficeDev on Twitter
Office Developer Blog

Provide feedback
We invite you to give us your feedback on the SharePoint Framework General Availability release. You can use the following resources to provide feedback directly to the SharePoint
engineering team:
sp-dev-docs repository issue list - Questions, issues, and comments.
SharePoint StackExchange - Tag with #spfx, #spfx-extensions, and #spfx-tooling.
SharePoint Developer - Microsoft Tech Community group.
SharePoint Developer UserVoice - Request new capabilities and features.

See also
Overview of the SharePoint Framework
SharePoint Framework development tools and libraries
Build your first SharePoint Framework Extension (Hello World part 1)
7/9/2018 • 6 minutes to read Edit Online

SharePoint Framework (SPFx) Extensions are client-side components that run inside the context of a SharePoint page. You can deploy extensions to SharePoint Online, and you can use
modern JavaScript tools and libraries to build them.
You can also follow the steps in this article by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/yrFNu6K7iuU

Create an extension project


1. Create a new project directory in your favorite location.

md app-extension

2. Go to the project directory.

cd app-extension

3. Create a new HelloWorld extension by running the Yeoman SharePoint Generator.

yo @microsoft/sharepoint

4. When prompted:
Accept the default app-extension as your solution name, and select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder, and select Enter.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select Extension as the client-side component type to be created.
Select Application Customizer as the extension type to be created.
5. The next set of prompts ask for specific information about your extension. When prompted:
Accept the default HelloWorld as your extension name, and select Enter.
Accept the default HelloWorld description as your extension description, and select Enter.
For the next question Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites...., ensure you select No (N) , and select Enter.
If you select Yes (y), the scaffolding will not generate the Elements.xml feature deployment file.
NOTE

If you use a name for the extension that is too long, you might encounter issues. The entries provided are used to generate an alias entry for the Application Customizer
manifest JSON file. If the alias is longer than 40 characters, you get an exception when you try to serve the extension by using gulp serve --nobrowser . You can resolve this by
updating the alias entry afterward.
At this point, Yeoman installs the required dependencies and scaffolds the solution files along with the HelloWorld extension. This might take a few minutes.
When the scaffold is complete, you should see the following message indicating a successful scaffold:

For information about troubleshooting any errors, see Known issues.


6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Next, type the following into the console to start Visual Studio Code.

code .

NOTE

Because the SharePoint client-side solution is HTML/TypeScript based, you can use any code editor that supports client-side development to build your extension.
Notice how the default solution structure looks like the solution structure for client-side web parts. This is the basic SharePoint Framework solution structure, with similar
configuration options across all solution types.

8. Open HelloWorldApplicationCustomizer.manifest.json in the src\extensions\helloWorld folder.


This file defines your extension type and a unique identifier for your extension. You’ll need this ID later when you debug and deploy your extension to SharePoint.
Code your Application Customizer
Open the HelloWorldApplicationCustomizer.ts file in the src\extensions\helloWorld folder.
Notice that base class for the Application Customizer is imported from the sp-application-base package, which contains SharePoint framework code required by the Application
Customizer.

The logic for your Application Customizer is contained in the onInit method, which is called when the client-side extension is first activated on the page. This event occurs after this.context
and this.properties are assigned. As with web parts, onInit() returns a promise that you can use to perform asynchronous operations.
NOTE

The class constructor is called at an early stage, when this.context and this.properties are undefined. Custom initiation logic is not supported here.
The following are the contents of onInit() in the default solution. This default solution writes a log to the Dev Dashboard, and then displays a simple JavaScript alert when the page renders.

If your Application Customizer uses the ClientSideComponentProperties JSON input, it is deserialized into the BaseExtension.properties object. You can define an interface to describe
it. The default template is looking for a property called testMessage. If that property is provided, it outputs it in an alert message.

Debug your Application Customizer


You cannot currently use the local Workbench to test SharePoint Framework Extensions. You need to test them against a live SharePoint Online site. You don't have to deploy your
customization to the app catalog to do this, which makes the debugging experience simple and efficient.
1. Compile your code and host the compiled files from your local computer by running the following command:

gulp serve --nobrowser

NOTE

If you do not have the SPFx developer certificate installed, Workbench notifies you that it is not configured to load scripts from localhost. If this happens, stop the process that is
currently running in the console window, run the gulp trust-dev-cert command in your project directory console to install the developer certificate, and then run the
gulp serve --nobrowser command again.

You use the --nobrowser option because you don't need to launch the local Workbench since you can't debug extensions locally.
When the code compiles without errors, it serves the resulting manifest from https://localhost:4321.
2. To test your extension, go to a modern list view page in your SharePoint environment and append the following query string parameters to the URL. Notice that you need to update
the ID to match your own extension identifier. This is available in the HelloWorldApplicationCustomizer.manifest.json file.

?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"e5625e23-5c5a-4007-a335-e6c2c3afa485":{"location":"ClientSideExtension.ApplicationCustomizer","p

More detail about the URL query parameters:


loadSPFX=true. Ensures that the SharePoint Framework is loaded on the page. For performance reasons, the framework does not load unless at least one extension is
registered. Because no components are registered, you must explicitly load the framework.
debugManifestsFile. Specifies that you want to load SPFx components that are locally served. The loader only looks for components in the app catalog (for your deployed
solution) and the SharePoint manifest server (for the system libraries).
customActions. Simulates a custom action. When you deploy and register this component in a site, you'll create this CustomAction object and describe all the different
properties you can set on it.
Key. Use the GUID of the extension as the key to associate with the custom action. This has to match the ID value of your extension, which is available in the extension
manifest.json file.
Location. The type of custom action. Use ClientSideExtension.ApplicationCustomizer for the Application Customizer extension.
Properties. An optional JSON object that contains properties that are available via the this.properties member. In this HelloWorld example, it defined a testMessage
property.
3. Go to a modern list in SharePoint Online. This can be either a list or a library. Application Customizers are also supported in modern pages and on the Site Contents page.
4. Extend the URL with the additional query parameters described. Notice that you need to update the GUID to match the ID of your custom Application Customizer.
The full URL should look similar to the following:

contoso.sharepoint.com/Lists/Contoso/AllItems.aspx?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"e5625e23-5c5a-4007-a335-e6c2c3afa485":{"location":

Alternatively, you can create serve configuration entries in the config/serve.json file in your project to automate the creation of the debug query string parameters as outlined in this
document: Debug SharePoint Framework solutions on modern SharePoint pages

1. Select Load debug scripts to continue loading scripts from your local host.

You should now see the dialog message on your page.

This dialog is thrown by your SharePoint Framework Extension. Note that because you provided the testMessage property as part of the debug query parameters, it's included in the
alert message. You can configure your extension instances based on the client component properties, which are passed for the instance in runtime mode.
NOTE

If you have issues with debugging, double-check the URL query parameters used for the query. Some browsers encode the parameters and in some scenarios this affects the behavior.

Next steps
Congratulations, you got your first SharePoint Framework Extension running!
To continue building out your extension, see Using page placeholders from Application Customizer (Hello World part 2). You use the same project and take advantage of specific content
placeholders for modifying the UI of SharePoint. Notice that the gulp serve command is still running in your console window (or in Visual Studio Code if you are using the editor). You can
continue to let it run while you go to the next article.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Build your first SharePoint Framework Extension (Hello World part 1)
7/9/2018 • 6 minutes to read Edit Online

SharePoint Framework (SPFx) Extensions are client-side components that run inside the context of a SharePoint page. You can deploy extensions to SharePoint Online, and you can use
modern JavaScript tools and libraries to build them.
You can also follow the steps in this article by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/yrFNu6K7iuU

Create an extension project


1. Create a new project directory in your favorite location.

md app-extension

2. Go to the project directory.

cd app-extension

3. Create a new HelloWorld extension by running the Yeoman SharePoint Generator.

yo @microsoft/sharepoint

4. When prompted:
Accept the default app-extension as your solution name, and select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder, and select Enter.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select Extension as the client-side component type to be created.
Select Application Customizer as the extension type to be created.
5. The next set of prompts ask for specific information about your extension. When prompted:
Accept the default HelloWorld as your extension name, and select Enter.
Accept the default HelloWorld description as your extension description, and select Enter.
For the next question Do you want to allow the tenant admin the choice of being able to deploy the solution to all sites...., ensure you select No (N) , and select Enter.
If you select Yes (y), the scaffolding will not generate the Elements.xml feature deployment file.
NOTE

If you use a name for the extension that is too long, you might encounter issues. The entries provided are used to generate an alias entry for the Application Customizer
manifest JSON file. If the alias is longer than 40 characters, you get an exception when you try to serve the extension by using gulp serve --nobrowser . You can resolve this by
updating the alias entry afterward.
At this point, Yeoman installs the required dependencies and scaffolds the solution files along with the HelloWorld extension. This might take a few minutes.
When the scaffold is complete, you should see the following message indicating a successful scaffold:

For information about troubleshooting any errors, see Known issues.


6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Next, type the following into the console to start Visual Studio Code.

code .

NOTE

Because the SharePoint client-side solution is HTML/TypeScript based, you can use any code editor that supports client-side development to build your extension.
Notice how the default solution structure looks like the solution structure for client-side web parts. This is the basic SharePoint Framework solution structure, with similar
configuration options across all solution types.

8. Open HelloWorldApplicationCustomizer.manifest.json in the src\extensions\helloWorld folder.


This file defines your extension type and a unique identifier for your extension. You’ll need this ID later when you debug and deploy your extension to SharePoint.
Code your Application Customizer
Open the HelloWorldApplicationCustomizer.ts file in the src\extensions\helloWorld folder.
Notice that base class for the Application Customizer is imported from the sp-application-base package, which contains SharePoint framework code required by the Application
Customizer.

The logic for your Application Customizer is contained in the onInit method, which is called when the client-side extension is first activated on the page. This event occurs after this.context
and this.properties are assigned. As with web parts, onInit() returns a promise that you can use to perform asynchronous operations.
NOTE

The class constructor is called at an early stage, when this.context and this.properties are undefined. Custom initiation logic is not supported here.
The following are the contents of onInit() in the default solution. This default solution writes a log to the Dev Dashboard, and then displays a simple JavaScript alert when the page renders.

If your Application Customizer uses the ClientSideComponentProperties JSON input, it is deserialized into the BaseExtension.properties object. You can define an interface to describe
it. The default template is looking for a property called testMessage. If that property is provided, it outputs it in an alert message.

Debug your Application Customizer


You cannot currently use the local Workbench to test SharePoint Framework Extensions. You need to test them against a live SharePoint Online site. You don't have to deploy your
customization to the app catalog to do this, which makes the debugging experience simple and efficient.
1. Compile your code and host the compiled files from your local computer by running the following command:

gulp serve --nobrowser

NOTE

If you do not have the SPFx developer certificate installed, Workbench notifies you that it is not configured to load scripts from localhost. If this happens, stop the process that is
currently running in the console window, run the gulp trust-dev-cert command in your project directory console to install the developer certificate, and then run the
gulp serve --nobrowser command again.

You use the --nobrowser option because you don't need to launch the local Workbench since you can't debug extensions locally.
When the code compiles without errors, it serves the resulting manifest from https://localhost:4321.
2. To test your extension, go to a modern list view page in your SharePoint environment and append the following query string parameters to the URL. Notice that you need to update
the ID to match your own extension identifier. This is available in the HelloWorldApplicationCustomizer.manifest.json file.

?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"e5625e23-5c5a-4007-a335-e6c2c3afa485":{"location":"ClientSideExtension.ApplicationCustomizer","p

More detail about the URL query parameters:


loadSPFX=true. Ensures that the SharePoint Framework is loaded on the page. For performance reasons, the framework does not load unless at least one extension is
registered. Because no components are registered, you must explicitly load the framework.
debugManifestsFile. Specifies that you want to load SPFx components that are locally served. The loader only looks for components in the app catalog (for your deployed
solution) and the SharePoint manifest server (for the system libraries).
customActions. Simulates a custom action. When you deploy and register this component in a site, you'll create this CustomAction object and describe all the different
properties you can set on it.
Key. Use the GUID of the extension as the key to associate with the custom action. This has to match the ID value of your extension, which is available in the extension
manifest.json file.
Location. The type of custom action. Use ClientSideExtension.ApplicationCustomizer for the Application Customizer extension.
Properties. An optional JSON object that contains properties that are available via the this.properties member. In this HelloWorld example, it defined a testMessage
property.
3. Go to a modern list in SharePoint Online. This can be either a list or a library. Application Customizers are also supported in modern pages and on the Site Contents page.
4. Extend the URL with the additional query parameters described. Notice that you need to update the GUID to match the ID of your custom Application Customizer.
The full URL should look similar to the following:

contoso.sharepoint.com/Lists/Contoso/AllItems.aspx?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"e5625e23-5c5a-4007-a335-e6c2c3afa485":{"location":

Alternatively, you can create serve configuration entries in the config/serve.json file in your project to automate the creation of the debug query string parameters as outlined in this
document: Debug SharePoint Framework solutions on modern SharePoint pages

1. Select Load debug scripts to continue loading scripts from your local host.

You should now see the dialog message on your page.

This dialog is thrown by your SharePoint Framework Extension. Note that because you provided the testMessage property as part of the debug query parameters, it's included in the
alert message. You can configure your extension instances based on the client component properties, which are passed for the instance in runtime mode.
NOTE

If you have issues with debugging, double-check the URL query parameters used for the query. Some browsers encode the parameters and in some scenarios this affects the behavior.

Next steps
Congratulations, you got your first SharePoint Framework Extension running!
To continue building out your extension, see Using page placeholders from Application Customizer (Hello World part 2). You use the same project and take advantage of specific content
placeholders for modifying the UI of SharePoint. Notice that the gulp serve command is still running in your console window (or in Visual Studio Code if you are using the editor). You can
continue to let it run while you go to the next article.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Use page placeholders from Application Customizer (Hello World part 2)
7/9/2018 • 6 minutes to read Edit Online

Application Customizers provide access to well-known locations on SharePoint pages that you can modify based on your business and functional requirements. For example, you can create
dynamic header and footer experiences that render across all the pages in SharePoint Online.
This model is similar to using a UserCustomAction collection in a Site or Web object to modify the page experience via custom JavaScript. The key difference or advantage with
SharePoint Framework (SPFx) Extensions is that your page elements won't change if changes are made to the HTML/DOM structure in SharePoint Online.
This article describes how to extend your Hello World extension to take advantage of page placeholders.
You can also follow these steps by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/3LXuYBaJ1Lc

Get access to page placeholders


Application Customizer extensions are supported with Site , Web , and List scopes. You can control the scope by deciding where or how the Application Customizer is registered in your
SharePoint tenant. When the Application Customizer exists in the scope and is being rendered, you can use the following method to get access to the placeholder.

// Handling the Bottom placeholder


if (!this._bottomPlaceholder) {
this._bottomPlaceholder =
this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Bottom,
{ onDispose: this._onDispose });
...
}

After you get the placeholder object, you have full control over what is presented to the end user.
Notice that you're requesting a well-known placeholder by using the corresponding well-known identifier. In this case, the code is accessing the footer section of the page by using the
Bottom identifier.

Modify the Application Customizer to access and modify placeholders by adding custom HTML elements
1. In Visual Studio Code (or your preferred IDE), open src\extensions\helloWorld\HelloWorldApplicationCustomizer.ts.
2. Add the PlaceholderContent and PlaceholderName to the import from @microsoft/sp-application-base by updating the import statement as follows:

import {
BaseApplicationCustomizer,
PlaceholderContent,
PlaceholderName
} from '@microsoft/sp-application-base';

Also add the following import statements after the strings import at the top of the file:

import styles from './AppCustomizer.module.scss';


import { escape } from '@microsoft/sp-lodash-subset';
You use escape to escape Application Customizer properties. You'll create style definitions for the output in the following steps.
3. Create a new file named AppCustomizer.module.scss under the src\extensions\helloWorld folder.
4. Update AppCustomizer.module.scss as follows:
NOTE

These are the styles that are used in the HTML output for the header and footer placeholders.

@import '~@microsoft/sp-office-ui-fabric-core/dist/sass/SPFabricCore.scss';

.app {
.top {
height:60px;
text-align:center;
line-height:2.5;
font-weight:bold;
display: flex;
align-items: center;
justify-content: center;
background-color: $ms-color-themePrimary;
color: $ms-color-white;

.bottom {
height:40px;
text-align:center;
line-height:2.5;
font-weight:bold;
display: flex;
align-items: center;
justify-content: center;
background-color: $ms-color-themePrimary;
color: $ms-color-white;
}
}

5. Install the @microsoft/sp-office-ui-fabric-core package to enable importing from SPFabricCore.scss

npm install @microsoft/sp-office-ui-fabric-core

6. In the HelloWorldApplicationCustomizer.ts file, update the IHelloWorldApplicationCustomizerProperties interface to add specific properties for Header and Footer, as
follows.
NOTE

If your Command Set uses the ClientSideComponentProperties JSON input, it is deserialized into the BaseExtension.properties object. You can define an interface to describe it.

export interface IHelloWorldApplicationCustomizerProperties {


Top: string;
Bottom: string;
}

7. Add the following private variables inside the HelloWorldApplicationCustomizer class. In this scenario, these can just be local variables in an onRender method, but if you want to
share them with other objects, define them as private variables.

export default class HelloWorldApplicationCustomizer


extends BaseApplicationCustomizer<IHelloWorldApplicationCustomizerProperties> {

// These have been added


private _topPlaceholder: PlaceholderContent | undefined;
private _bottomPlaceholder: PlaceholderContent | undefined;

8. Update the onInit method code as follows:

@override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, `Initialized ${strings.Title}`);

// Added to handle possible changes on the existence of placeholders.


this.context.placeholderProvider.changedEvent.add(this, this._renderPlaceHolders);

// Call render method for generating the HTML elements.


this._renderPlaceHolders();
return Promise.resolve<void>();
}

9. Create a new _renderPlaceHolders private method with the following code:


private _renderPlaceHolders(): void {

console.log('HelloWorldApplicationCustomizer._renderPlaceHolders()');
console.log('Available placeholders: ',
this.context.placeholderProvider.placeholderNames.map(name => PlaceholderName[name]).join(', '));

// Handling the top placeholder


if (!this._topPlaceholder) {
this._topPlaceholder =
this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Top,
{ onDispose: this._onDispose });

// The extension should not assume that the expected placeholder is available.
if (!this._topPlaceholder) {
console.error('The expected placeholder (Top) was not found.');
return;
}

if (this.properties) {
let topString: string = this.properties.Top;
if (!topString) {
topString = '(Top property was not defined.)';
}

if (this._topPlaceholder.domElement) {
this._topPlaceholder.domElement.innerHTML = `
<div class="${styles.app}">
<div class="${styles.top}">
<i class="ms-Icon ms-Icon--Info" aria-hidden="true"></i> ${escape(topString)}
</div>
</div>`;
}
}
}

// Handling the bottom placeholder


if (!this._bottomPlaceholder) {
this._bottomPlaceholder =
this.context.placeholderProvider.tryCreateContent(
PlaceholderName.Bottom,
{ onDispose: this._onDispose });

// The extension should not assume that the expected placeholder is available.
if (!this._bottomPlaceholder) {
console.error('The expected placeholder (Bottom) was not found.');
return;
}

if (this.properties) {
let bottomString: string = this.properties.Bottom;
if (!bottomString) {
bottomString = '(Bottom property was not defined.)';
}

if (this._bottomPlaceholder.domElement) {
this._bottomPlaceholder.domElement.innerHTML = `
<div class="${styles.app}">
<div class="${styles.bottom}">
<i class="ms-Icon ms-Icon--Info" aria-hidden="true"></i> ${escape(bottomString)}
</div>
</div>`;
}
}
}
}

Note the following about this code:


Use this.context.placeholderProvider.tryCreateContent to get access to the placeholder.
Extension code should not assume that the expected placeholder is available.
The code expects custom properties called Top and Bottom . If the properties exist, they render inside the placeholders.
Notice that the code path for both the top and bottom placeholders is almost identical. The only differences are the variables used and the style definitions.
It is possible to use the class names defined in the style sheet directly but it is not recommended. In case no style sheet reference defined in the styles variable is found in the
code, the style sheet won't get added to the page. This is because unused references will get removed during bild process.
10. Add the following method after the _renderPlaceHolders method. In this case, you simply output a console message when the extension is removed from the page.

private _onDispose(): void {


console.log('[HelloWorldApplicationCustomizer._onDispose] Disposed custom top and bottom placeholders.');
}

You're now ready to test your code in SharePoint Online.

Test your code


1. Switch to the console window that is running gulp serve and check for errors. Gulp reports any errors in the console; you'll need to fix them before you proceed.
If you don't have the solution running, use the following command to check for errors.

gulp serve --nobrowser

2. Go to a modern list in SharePoint Online. This can be a list or a library. To test your extension, append the following query string parameters to the URL:
?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"e5625e23-5c5a-4007-a335-e6c2c3afa485":{"location":"ClientSideExtension.ApplicationCustomizer","p

Notice that the GUID used in this query parameter has to match the ID attribute of your Application Customizer. This is available in the
HelloWorldApplicationCustomizer.manifest.json file.
You use Header and Footer JSON properties to provide parameters or configurations to the Application Customizer. In this case, you simply output these values. You can also
adjust the behavior based on the properties used in production.
The full URL should look similar to the following:

contoso.sharepoint.com/Lists/Contoso/AllItems.aspx?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"e5625e23-5c5a-4007-a335-e6c2c3afa485":{"locat

Alternatively, you can create serve configuration entries in the config/serve.json file in your project to automate the creation of the debug query string parameters as outlined in this
document: Debug SharePoint Framework solutions on modern SharePoint pages

1. Select Load debug scripts to continue loading scripts from your local host.

You should now see the custom header and footer content in your page.

Next steps
Congratulations, you built your own custom header and footer using the Application Customizer!
To continue building out your extension, see Deploy your extension to SharePoint (Hello World part 3). You will learn how to deploy and preview the Hello World extension in a SharePoint
site collection without using Debug query parameters.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Deploy your extension to SharePoint (Hello World part 3)
4/20/2018 • 5 minutes to read Edit Online

This article describes how to deploy your SharePoint Framework Application Customizer to SharePoint and see it working on modern SharePoint pages.
Be sure you have completed the procedures in the following articles before you begin:
Build your first SharePoint Framework Extension (Hello World part 1)
Use page placeholders from Application Customizer (Hello World part 2)
You can also follow these steps by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/DzHdVxL A3Pc

Package the Hello World Application Customizer


1. In the console window, go to the extension project directory created in Build your first SharePoint Framework Extension (Hello World part 1).

cd app-extension

2. If gulp serve is still running, stop it from running by selecting Ctrl+C.


Unlike in Debug mode, to use an extension on modern SharePoint server-side pages, you need to deploy and register the extension with SharePoint in Site collection , Site , or
List scope. The scope defines where and how the Application Customizer is active. In this particular scenario, we'll register the Application Customizer by using the Site collection
scope.
Before we package our solution, we want to include the code needed to automate the extension activation within the site whenever the solution is installed on the site. In this case, we'll
use feature framework elements to perform these actions directly in the solution package, but you could also associate the Application Customizer to a SharePoint site by using REST
or CSOM as part of the site provisioning, for example.
3. Install the solution package to the site where it should be installed so that the extension manifest is being white listed for execution.
4. Associate the Application Customizer to the planned scope. This can be performed programmatically (CSOM/REST) or by using the feature framework inside of the SharePoint
Framework solution package. You need to associate the following properties in the UserCustomAction object at the site collection, site, or list level.
ClientSideComponentId. This is the identifier (GUID) of the Field Customizer, which has been installed in the app catalog.
ClientSideComponentProperties. This is an optional parameter, which can be used to provide properties for the Field Customizer instance.
Note that you can control the requirement to add a solution containing your extension to the site by using the skipFeatureDeployment setting in package-solution.json. Even though
you would not require the solution to be installed on the site, you need to associate ClientSideComponentId to specific objects for the extension to be visible.
In the following steps, we'll review the CustomAction definition, which was automatically created for the solution as part of the scaffolding for enabling the solution on a site when it's
being installed.
5. Return to your solution package in Visual Studio Code (or to your preferred editor).
6. Extend the sharepoint folder and assets subfolder in the root of the solution to see the existing elements.xml file.
Review the existing elements.xml file for SharePoint definitions
Review the existing XML structure in the elements.xml file. Note that the ClientSideComponentId property has been automatically updated based on the unique ID of your Application
Customizer available in the HelloWorldApplicationCustomizer.manifest.json file in the src\extensions\helloWorld folder.
ClientSideComponentProperties has also been automatically set with the default structure and JSON properties for this extension instance. Note how the JSON is escaped so that we can
set it properly within an XML attribute.
The configuration uses the specific location of ClientSideExtension.ApplicationCustomizer to define that this is an Application Customizer. Because this elements.xml is associated to a Web
scoped feature by default, this CustomAction is automatically added to the Web.UserCustomAction collection in the site where the solution is being installed.
To ensure that the configuration matches updates performed in the Application Customizer, update the ClientSideComponentProperties as in the following XML structure. Note that you
should not copy the whole structure because it would cause a mismatch with your ClientSideComponentId.

<?xml version="1.0" encoding="utf-8"?>


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<CustomAction
Title="SPFxApplicationCustomizer"
Location="ClientSideExtension.ApplicationCustomizer"
ClientSideComponentId="46606aa6-5dd8-4792-b017-1555ec0a43a4"
ClientSideComponentProperties="{&quot;Top&quot;:&quot;Top area of the page&quot;,&quot;Bottom&quot;:&quot;Bottom area in the page&quot;}">

</CustomAction>

</Elements>

Ensure that definitions are taken into account within the build pipeline
Open package-solution.json from the config folder. The package-solution.json file defines the package metadata as shown in the following code. To ensure that the element.xml file is
taken into account while the solution is being packaged, default scaffolding adds needed configuration to define a feature framework feature definition for the solution package.

{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "app-extension-client-side-solution",
"id": "98a9fe4f-175c-48c1-adee-63fb927faa70",
"version": "1.0.0.0",
"features": [
{
"title": "Application Extension - Deployment of custom action.",
"description": "Deploys a custom action with ClientSideComponentId association",
"id": "4678966b-de68-445f-a74e-e553a7b937ab",
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml"
]
}
}
]
},
"paths": {
"zippedPackage": "solution/app-extension.sppkg"
}
}

Deploy the extension to SharePoint Online and host JavaScript from local host
Now you are ready to deploy the solution to a SharePoint site and have the CustomAction automatically associated on the site level.
1. In the console window, enter the following command to package your client-side solution that contains the extension so that we get the basic structure ready for packaging:
gulp bundle

2. Execute the following command so that the solution package is created:

gulp package-solution

The command creates the package in the sharepoint/solution folder:

app-extension.sppkg

3. You now need to deploy the package that was generated to the app catalog. To do this, go to your tenant's app catalog and open the Apps for SharePoint library.
4. Upload or drag and drop the app-extension.sppkg located in the sharepoint/solution folder to the app catalog. SharePoint displays a dialog and asks you to trust the client-side
solution.
Note that we did not update the URLs for hosting the solution for this deployment, so the URL is still pointing to https://localhost:4321 .
5. Select the Deploy button.

6. Move back to your console and ensure that the solution is running. If it's not running, execute the following command in the solution folder:

gulp serve --nobrowser

7. Go to the site where you want to test SharePoint asset provisioning. This could be any site collection in the tenant where you deployed this solution package.
8. Select the gear icon on the top navigation bar on the right, and then select Add an app to go to your Apps page.
9. In the Search box, enter app, and then select Enter to filter your apps.

10. Select the app-extension-client-side-solution app to install the solution on the site. When the installation is completed, refresh the page by selecting F5.
When the application has been successfully installed, you can see the header and footer being rendered just like with the debug query parameters.
Next steps
Congratulations, you have deployed an extension to a modern SharePoint page from the app catalog!
You can continue building out your Hello World extension in the next topic, Host extension from Office 365 CDN (Hello World part 4), where you learn how to deploy and load the extension
assets from a CDN instead of from localhost.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.
Host extension from Office 365 CDN (Hello World part 4)
4/20/2018 • 3 minutes to read Edit Online

This article describes how to deploy your SharePoint Framework Application Customizer to be hosted from an Office 365 CDN and how to deploy that to SharePoint for end users.
Be sure you have completed the procedures in the following articles before you begin:
Build your first SharePoint Framework Extension (Hello World part 1)
Use page placeholders from Application Customizer (Hello World part 2)
Deploy your extension to SharePoint (Hello World part 3)
You can also follow these steps by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/nh1qFArXG2Y

Enable the CDN in your Office 365 tenant


Office 365 CDN is the easiest way to host SharePoint Framework solutions directly from your tenant while still taking advantage of the Content Delivery Network (CDN) service for faster
load times of your assets.
1. Download the SharePoint Online Management Shell to ensure that you have the latest version.
2. Connect to your SharePoint Online tenant by using PowerShell:

Connect-SPOService -Url https://contoso-admin.sharepoint.com

3. Get the current status of public CDN settings from the tenant level by executing the following commands one-by-one:

Get-SPOTenantCdnEnabled -CdnType Public


Get-SPOTenantCdnOrigins -CdnType Public
Get-SPOTenantCdnPolicies -CdnType Public

4. Enable public CDN in the tenant:

Set-SPOTenantCdnEnabled -CdnType Public

Public CDN has now been enabled in the tenant by using the default file type configuration allowed. This means that the following file type extensions are supported: CSS, EOT, GIF,
ICO, JPEG, JPG, JS, MAP, PNG, SVG, TTF, and WOFF.
5. Open up a browser and move to a site collection where you'd like to host your CDN library. This could be any site collection in your tenant. In this tutorial, we create a specific library
to act as your CDN library, but you can also use a specific folder in any existing document library as the CDN endpoint.
6. Create a new document library on your site collection called CDN, and add a folder named helloworld to it.
7. In the PowerShell console, add a new CDN origin. In this case, we are setting the origin as */cdn , which means that any relative folder with the name of cdn acts as a CDN origin.

Add-SPOTenantCdnOrigin -CdnType Public -OriginUrl */cdn

8. Execute the following command to get the list of CDN origins from your tenant:

Get-SPOTenantCdnOrigins -CdnType Public

Note that your newly added origin is listed as a valid CDN origin. Final configuration of the origin takes approximately 15 minutes, so we can continue creating your test extension,
which is hosted from the origin after deployment is completed.

When the origin is listed without the (configuration pending) text, it is ready to be used in your tenant. This indicates an on-going configuration between SharePoint Online and the
CDN system.

Update your solution project for the CDN URLs


1. Return to the previously created solution to perform the needed URL updates.
2. Update the write-manifests.json file (under the config folder) as follows to point to your CDN endpoint. Use publiccdn.sharepointonline.com as the prefix, and then extend the URL
with the actual path of your tenant. The format of the CDN URL is as follows:

https://publiccdn.sharepointonline.com/<tenant host name>/sites/site/library/folder

3. Save your changes.


4. Execute the following tasks to bundle your solution. This executes a release build of your project using the CDN URL specified in the write-manifests.json file. The output of this
command is located in the ./temp/deploy folder. These are the files that you need to upload to the SharePoint folder acting as your CDN endpoint.

gulp bundle --ship

5. Execute the following task to package your solution. This command creates an app-extension.sppkg package in the sharepoint/solution folder, and prepares the assets in the
temp/deploy folder to be deployed to the CDN.

gulp package-solution --ship

6. Upload or drag-and-drop the newly created client-side solution package to the app catalog in your tenant, and then select the Deploy button.
7. Upload or drag-and-drop the files in the temp/deploy folder to the CDN/helloworld folder created earlier.
8. Install the new version of the solution to your site, and ensure that it's working properly without your locahost hosting the JavaScript file.

Congratulations, you have enabled a public CDN in your Office 365 tenant and taken advantage of it from your solution!
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.

See also
Build your first ListView Command Set extension
Build your first Field Customizer extension
Overview of SharePoint Framework Extensions
Build your first Field Customizer extension
7/9/2018 • 11 minutes to read Edit Online

Extensions are client-side components that run inside the context of a SharePoint page. Extensions can be deployed to SharePoint Online, and you can use modern JavaScript tools and
libraries to build them.
You can follow these steps by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/4wgZy5tm4yo

Create an extension project


1. Create a new project directory in your favorite location.

md field-extension

2. Go to the project directory.

cd field-extension

3. Create a new HelloWorld extension by running the Yeoman SharePoint Generator.

yo @microsoft/sharepoint

4. When prompted:
Accept the default value of field-extension as your solution name, and then select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder, and select Enter.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select Extension as the client-side component type to be created.
Select Field Customizer as the extension type to be created.
5. The next set of prompts ask for specific information about your extension:
Accept the default value of HelloWorld as your extension name, and then select Enter.
Accept the default value of HelloWorld description as your extension description, and select Enter.
Accept the default value of No JavaScript Framework as the framework selection, and select Enter.
At this point, Yeoman installs the required dependencies and scaffolds the solution files along with the HelloWorld extension. This might take a few minutes.
When the scaffold is complete, you should see the following message indicating a successful scaffold:

For information about troubleshooting any errors, see Known issues.


6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Type the following into the console to start Visual Studio Code.

code .

NOTE

Because the SharePoint client-side solution is HTML/TypeScript based, you can use any code editor that supports client-side development to build your extension.
Note how the default solution structure looks like the solution structure of client-side web parts. This is the basic SharePoint Framework solution structure, with similar configuration
options across all solution types.
8. Open HelloWorldFieldCustomizer.manifest.json in the src\extensions\helloWorld folder.
This file defines your extension type and a unique identifier id for your extension. You need this unique identifier later when debugging and deploying your extension to SharePoint.

Code your Field Customizer


Open the HelloWorldFieldCustomizer.ts file in the src\extensions\helloWorld folder.
Notice that the base class for the Field Customizer is imported from the sp-listview-extensibility package, which contains SharePoint Framework code required by the Field Customizer.

import { Log } from '@microsoft/sp-core-library';


import { override } from '@microsoft/decorators';
import {
BaseFieldCustomizer,
IFieldCustomizerCellEventParameters
} from '@microsoft/sp-listview-extensibility';

The logic for your Field Customizer is contained in the OnInit(), onRenderCell(), and onDisposeCell() methods.
onInit() is where you should perform any setup needed for your extension. This event occurs after this.context and this.properties are assigned, but before the page DOM is ready.
As with web parts, onInit() returns a promise that you can use to perform asynchronous operations; onRenderCell() is not called until your promise has resolved. If you don’t need that,
simply return Promise.resolve<void>(); .
onRenderCell() occurs when each cell is rendered. It provides an event.domElement HTML element where your code can write its content.
onDisposeCell() occurs immediately before the event.cellDiv is deleted. It can be used to free any resources that were allocated during field rendering. For example, if onRenderCell()
mounted a React element, onDisposeCell() must be used to free it; otherwise, a resource leak would occur. 
The following are the contents of onRenderCell() and onDisposeCell() in the default solution:

@override
public onRenderCell(event: IFieldCustomizerCellEventParameters): void {
// Use this method to perform your custom cell rendering.
const text: string = `${this.properties.sampleText}: ${event.fieldValue}`;

event.domElement.innerText = text;

event.domElement.classList.add(styles.cell);
}

@override
public onDisposeCell(event: IFieldCustomizerCellEventParameters): void {
// This method should be used to free any resources that were allocated during rendering.
// For example, if your onRenderCell() called ReactDOM.render(), then you should
// call ReactDOM.unmountComponentAtNode() here.
super.onDisposeCell(event);
}

Debug your Field Customizer


You cannot currently use the local Workbench to test SharePoint Framework Extensions. You need to test and develop them directly against a live SharePoint Online site. You don't have to
deploy your customization to the app catalog to do this, which makes the debugging experience simple and efficient.
1. Compile your code and host the compiled files from the local machine by running this command:

gulp serve --nobrowser

You use the --nobrowser option because you don't need to launch the local Workbench, since you can't debug extensions locally.
When the code compiles without errors, it serves the resulting manifest from https://localhost:4321.
2. To test your extension, go to a site in your SharePoint Online tenant.
3. Move to the Site Contents page.
4. On the toolbar, select New, and then select List.

5. Create a new list named Orders, and then select Create.

6. Select the plus sign, and then select Number to create a new Number field for the list.

7. Set the name of the field to Percent, and then select Save.
8. Add a few items with different numbers in the percent field. We'll modify the rendering later in this tutorial, so the different numbers will be presented differently based on your
custom implementation.

Because our Field Customizer is still hosted in localhost and is running, we can use specific debug query parameters to execute the code in the newly created list.
9. Append the following query string parameters to the URL. Notice that you need to update the ID to match your own extension identifier available from the
HelloWorldFieldCustomizer.manifest.json file. For more information, see More details about the URL query parameters.

?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&fieldCustomizers={"Percent":{"id":"45a1d299-990d-4917-ba62-7cb67158be16","properties":{"sampleText":"Hello!"}}}

The full URL should look similar to the following, depending on your tenant URL and the location of the newly created list:

contoso.sharepoint.com/Lists/Orders/AllItems.aspx?loadSPFX=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&fieldCustomizers={"Percent":{"id":"45a1d299-990d-4917-ba62-7cb67158

Alternatively, you can create serve configuration entries in the config/serve.json file in your project to automate the creation of the debug query string parameters as outlined in this
document: Debug SharePoint Framework solutions on modern SharePoint pages

1. Accept the loading of debug manifests by selecting Load debug scripts when prompted.

Notice how the Percent values are now presented with an additional prefix string as Hello!: , which is provided as a property for the Field Customizer.
More details about the URL query parameters
loadSPFX=true ensures that the SharePoint Framework is loaded on the page. For performance reasons, the framework is not normally loaded unless at least one extension is
registered. Because no components are registered yet, we must explicitly load the framework.
debugManifestsFile specifies that you want to load SPFx components that are locally served. The loader only looks for components in the app catalog (for your deployed solution) and
the SharePoint manifest server (for the system libraries).
fieldCustomizers indicates which fields in your list should have their rendering controlled by the Field Customizer. The ID parameter specifies the GUID of the extension that should be
used to control the rendering of the field. The properties parameter is an optional text string containing a JSON object that is deserialized into this.properties for your extension.
Key: Use the internal name of the field as the key.
Id: The GUID of the Field Customizer extension associated with this field.
Properties: The property values defined in the extension. In this example, sampleText is a property defined by the extension.

Enhance the Field Customizer rendering


Now that we have successfully tested the out-of-the-box starting point of the Field Customizer, let's modify the logic slightly to have a more polished rendering of the field value.
1. Open the HelloWorld.module.scss file in the src\extensions\helloWorld folder, and update the styling definition as follows.

.HelloWorld {
.cell {
display: inline-block;
}
.full {
background-color: #e5e5e5;
width: 100px;
}
}

2. Open the HelloWorldFieldCustomizer.ts file in the src\extensions\helloWorld folder, and update the onRenderCell method as follows.

@override
public onRenderCell(event: IFieldCustomizerCellEventParameters): void {

event.domElement.classList.add(styles.cell);
event.domElement.innerHTML = `
<div class='${styles.HelloWorld}'>
<div class='${styles.full}'>
<div style='width: ${event.fieldValue}px; background:#0094ff; color:#c0c0c0'>
&nbsp; ${event.fieldValue}
</div>
</div>
</div>`;
}

3. In your console window, ensure that you do not have any exceptions. If you do not have the solution running in localhost, execute the following command:

gulp serve --nobrowser

4. In your previously created list, use the same query parameter as used previously, with the field being Percent and the ID being updated to your extension identifier available from
the HelloWorldFieldCustomizer.manifest.json file.
5. Accept the loading of debug manifests by selecting Load debug scripts when prompted.
Note how we changed the field rendering style completely. The field value is indicated by using a graphical representation of the value.

Add the field definition to the solution package for deployment


Now that we have tested our solution properly in debug mode, we can package this to be deployed automatically as part of the solution package deployed to the sites.
1. Install the solution package to the site where it should be installed, so that the extension manifest is white listed for execution.
2. Associate the Field Customizer to an existing field in the site. You can do this programmatically (CSOM/REST) or by using the feature framework inside of the SharePoint Framework
solution package. You need to associate the following properties in the SPField object at the site or list level.
ClientSiteComponentId is the identifier (GUID) of the Field Customizer, which has been installed in the app catalog.
ClientSideComponentProperties is an optional parameter, which can be used to provide properties for the Field Customizer instance.
Note that you can control the requirement to add a solution containing your extension to the site by using the skipFeatureDeployment setting in package-solution.json. Even
though you would not require the solution to be installed on the site, you'd need to associate ClientSideComponentId to specific objects for the extension to be visible.
In the following steps, we review the default field definition, which was automatically created and will then be used to automatically deploy needed configurations when the
solution package is installed on a site.
3. Return to your solution in Visual Studio Code (or to your preferred editor).
4. Extend the sharepoint folder and assets subfolder in the root of the solution to see the existing elements.xml file.

Review the elements.xml file


Open the elements.xml file inside the sharepoint\assets folder.
Note the following XML structure in elements.xml. The ClientSideComponentId property has been automatically updated to the unique ID of your Field Customizer available in the
HelloWorldFieldCustomizer.manifest.json file in the src\extensions\helloWorld folder.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<Field ID="{060E50AC-E9C1-3D3C-B1F9-DE0BCAC200F6}"
Name="SPFxPercentage"
DisplayName="Percentage"
Type="Number"
Min="0"
Required="FALSE"
Group="SPFx Columns"
ClientSideComponentId="7e7a4262-d02b-49bf-bfcb-e6ef1716aaef">
</Field>

</Elements>

Ensure that definitions are taken into account within the build pipeline
Open package-solution.json from the config folder. The package-solution.json file defines the package metadata as shown in the following code. To ensure that the element.xml file is
taken into account while the solution is being packaged, default scaffolding added needed configuration to define a feature framework feature definition for the solution package.

{
"solution": {
"name": "field-extension-client-side-solution",
"id": "11cd343e-1ce6-462c-8acb-929804d0c3b2",
"version": "1.0.0.0",
"skipFeatureDeployment": false,
"features": [{
"title": "Field Extension - Deployment of custom field.",
"description": "Deploys a custom field with ClientSideComponentId association",
"id": "123fe847-ced2-3036-b564-8dad5c6c6e83",
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml"
]
}
}]
},
"paths": {
"zippedPackage": "solution/field-extension.sppkg"
}
}

Deploy the field to SharePoint Online and host JavaScript from local host
Now you are ready to deploy the solution to a SharePoint site and get the field association automatically included in a field.
1. In the console window, enter the following command to package your client-side solution that contains the extension so that we get the basic structure ready for packaging:

gulp bundle

2. Execute the following command so that the solution package is created:

gulp package-solution

The command creates the package in the sharepoint/solution folder:

field-extension.sppkg

3. You now need to deploy the package that was generated to the app catalog. To do this, go to your tenant's app catalog and open the Apps for SharePoint library.
4. Upload or drag-and-drop the field-extension.sppkg located in the sharepoint/solution folder to the app catalog. SharePoint displays a dialog and asks you to trust the client-side
solution.
Note that we did not update the URLs for hosting the solution for this deployment, so the URL is still pointing to https://localhost:4321 .
5. Select the Deploy button.

6. Go to the site where you want to test SharePoint asset provisioning. This could be any site collection in the tenant where you deployed this solution package.
7. Select the gears icon on the top navigation bar on the right, and then select Add an app to go to your Apps page.
8. In the Search box, enter field, and then select Enter to filter your apps.
9. Select the field-extension-client-side-solution app to install the solution on the site. After the installation is complete, refresh the page by selecting F5.
10. When the solution has been installed, select New from the toolbar on the Site Contents page, and then select List.

11. Create a list named Invoices.


12. When the new list is created, on the Site Contents page, select Settings from the menu of the newly created list.

13. Under Columns, select Add from existing site columns.


14. Under the SPFx Columns group, select the Percentage field that was provisioned from the solution package, and then select OK.

15. On your console, ensure that the solution is running. If it's not running, execute the following command in the solution folder:

gulp serve --nobrowser

16. Go to the newly created Invoices list. Add a few items to the list with different values in the Percentage column to determine how the field is rendering without the debug query
parameters.
In this case, we continued to host the JavaScript from the localhost, but you could just as well relocate the assets to any CDN and update the URL to enable the loading of the JavaScript
assets outside of the localhost as well.
The process for publishing your app is identical among the different extension types. You can use the following publishing steps to update the assets to be hosted from a CDN: Host
extension from Office 365 CDN.
NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.

See also
Build your first ListView Command Set extension
Overview of SharePoint Framework Extensions
Build your first ListView Command Set extension
7/9/2018 • 11 minutes to read Edit Online

Extensions are client-side components that run inside the context of a SharePoint page. Extensions can be deployed to SharePoint Online, and you can use modern JavaScript tools and
libraries to build them.
You can follow these steps by watching the video on the SharePoint PnP YouTube Channel:

https://www.youtube-nocookie.com/embed/JBhgdSgWgdM

Create an extension project


1. Create a new project directory in your favorite location.

md command-extension

2. Go to the project directory.

cd command-extension

3. Create a new HelloWorld extension by running the Yeoman SharePoint Generator.

yo @microsoft/sharepoint

4. When prompted:
Accept the default value of command-extension as your solution name, and then select Enter.
Select SharePoint Online only (latest), and select Enter.
Select Use the current folder, and select Enter.
Select N to require the extension to be installed on each site explicitly when it's being used.
Select Extension as the client-side component type to be created.
Select ListView Command Set as the extension type to be created.
5. The next set of prompts ask for specific information about your extension:
Accept the default value of HelloWorld as your extension name, and then select Enter.
Accept the default value of HelloWorld description as your extension description, and select Enter.
At this point, Yeoman installs the required dependencies and scaffolds the solution files along with the HelloWorld extension. This might take a few minutes.
When the scaffold is complete, you should see the following message indicating a successful scaffold:

For information about troubleshooting any errors, see Known issues.


6. After the scaffolding completes, lock down the version of the project dependencies by running the following command:

npm shrinkwrap

7. Next, type the following into the console to start Visual Studio Code.

code .

NOTE

Because the SharePoint client-side solution is HTML/TypeScript based, you can use any code editor that supports client-side development to build your extension.
Note how the default solution structure looks like the solution structure of client-side web parts. This is the basic SharePoint Framework solution structure, with similar configuration
options across all solution types.

8. Open HelloWorldCommandSet.manifest.json in the src\extensions\helloWorld folder.


This file defines your extension type and a unique identifier id for your extension. You need this unique identifier later when debugging and deploying your extension to SharePoint.
Note the actual command definitions in the manifest file. These are the actual buttons that are exposed based on the registration target. In the default template, you find two different
buttons: Command One and Command Two.

Currently, images are not properly referenced unless you are referring to them from absolute locations in a CDN within your manifest. This will be improved in future releases.

Code your ListView Command Set


Open the HelloWorldCommandSet.ts file in the src\extensions\helloWorld folder.
Notice that the base class for the ListView Command Set is imported from the sp-listview-extensibility package, which contains SharePoint Framework code required by the ListView
Command Set.

import { override } from '@microsoft/decorators';


import { Log } from '@microsoft/sp-core-library';
import {
BaseListViewCommandSet,
Command,
IListViewCommandSetListViewUpdatedParameters,
IListViewCommandSetExecuteEventParameters
} from '@microsoft/sp-listview-extensibility';
import { Dialog } from '@microsoft/sp-dialog';

The behavior for your custom buttons is contained in the onListViewUpdated() and OnExecute() methods.
The onListViewUpdated() event occurs separately for each command (for example, a menu item) whenever a change happens in the ListView, and the UI needs to be re-rendered. The
event function parameter represents information about the command being rendered. The handler can use this information to customize the title or adjust the visibility, for example, if a
command should only be shown when a certain number of items are selected in the list view. This is the default implementation.
When using the method tryGetCommand , you get a Command object, which is a representation of the command that shows in the UI. You can modify its values, such as title , or visible , to
modify the UI element. SPFx uses this information when re-rendering the commands. These objects keep the state from the last render, so if a command is set to visible = false , it remains
invisible until it is set back to visible = true .

@override
public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
if (compareOneCommand) {
// This command should be hidden unless exactly one row is selected.
compareOneCommand.visible = event.selectedRows.length === 1;
}
}

The OnExecute() method defines what happens when a command is executed (for example, the menu item is selected). In the default implementation, different messages are shown based
on which button was selected.
@override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`${this.properties.sampleTextOne}`);
break;
case 'COMMAND_2':
Dialog.alert(`${this.properties.sampleTextTwo}`);
break;
default:
throw new Error('Unknown command');
}
}

Debug your ListView Command Set


You cannot currently use the local Workbench to test SharePoint Framework Extensions. You'll need to test and develop them directly against a live SharePoint Online site. You don't have to
deploy your customization to the app catalog to do this, which makes the debugging experience simple and efficient.
1. Compile your code and host the compiled files from the local machine by running this command:

gulp serve --nobrowser

You use the --nobrowser option because you don't need to launch the local Workbench, since you can't debug extensions locally.
When the code compiles without errors, it serves the resulting manifest from https://localhost:4321.
2. Go to any SharePoint list in your SharePoint Online site by using the modern experience.
Because our ListView Command Set is hosted from localhost and is running, we can use specific debug query parameters to execute the code in the list view.
3. Append the following query string parameters to the URL. Notice that you need to update the GUID to match the ID of your ListView Command Set Extension available in the
HelloWorldCommandSet.manifest.json file. For more information, see More details about the URL query parameters.

?loadSpfx=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"a8047e2f-30d5-40fc-b880-b2890c7c16d6":{"location":"ClientSideExtension.ListViewCommandSet.CommandBar

The full URL should look similar to the following, depending on your tenant URL and the location of the list.

contoso.sharepoint.com/Lists/Orders/AllItems.aspx?loadSpfx=true&debugManifestsFile=https://localhost:4321/temp/manifests.js&customActions={"a8047e2f-30d5-40fc-b880-b2890c7c16d6":{"location":"

Alternatively, you can create serve configuration entries in the config/serve.json file in your project to automate the creation of the debug query string parameters as outlined in this
document: Debug SharePoint Framework solutions on modern SharePoint pages

1. Accept the loading of debug manifests by selecting Load debug scripts when prompted.

2. Notice the new Command Two button available in the toolbar. Select that button to see the text provided as property for the sampleTextTwo property.

3. The Command One button is not visible based on the code, until one row is selected in the document library. Upload or create a document to the library and confirm that the second
button is visible.
4. Select Command Two to see how the dialog control works, which is used in the default output from the solution scaffolding when the ListView Command Set is selected as the
extension type.

More details about the URL query parameters


loadSPFX=true ensures that the SharePoint Framework is loaded on the page. For performance reasons, the framework is not normally loaded unless at least one extension is
registered. Because no components are registered yet, we must explicitly load the framework.
debugManifestsFile specifies that we want to load SPFx components that are being locally served. The loader only looks for components in the app catalog (for your deployed solution)
and the SharePoint manifest server (for the system libraries).
customActions simulates a custom action. You can set many properties on this CustomAction object that affect the look, feel, and location of your button; we’ll cover them all later.
Key: GUID of the extension.
Location: Where the commands are displayed. The possible values are:
ClientSideExtension.ListViewCommandSet.ContextMenu: The context menu of the item(s).
ClientSideExtension.ListViewCommandSet.CommandBar: The top command set menu in a list or library.
ClientSideExtension.ListViewCommandSet: Both the context menu and the command bar (corresponds to SPUserCustomAction.Location="CommandUI.Ribbon").
Properties: An optional JSON object containing properties that are available via the this.properties member.

Enhance the ListView Command Set rendering


The default solution takes advantage of a new Dialog API, which can be used to show modal dialogs easily from your code. In the following steps, we'll slightly modify the default experience
to demonstrate Dialog API use cases.
1. Return to the console, and execute the following command to include the Dialog API in our solution.
2. Return to Visual Studio Code (or your preferred editor).
3. Open HelloWorldCommandSet.ts from the src\extensions\helloWorld folder.
4. Ensure at the top of the file the following import statement is present for the Dialog class from @microsoft/sp-dialog . It should already be there, if not, add it.

import { Dialog } from '@microsoft/sp-dialog';

1. Update the onExecute method as follows:


@override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`Clicked ${strings.Command1}`);
break;
case 'COMMAND_2':
Dialog.prompt(`Clicked ${strings.Command2}. Enter something to alert:`).then((value: string) => {
Dialog.alert(value);
});
break;
default:
throw new Error('Unknown command');
}
}

2. In your console window, ensure that you do not have any exceptions. If you do not already have the solution running in localhost, execute the following command:

gulp serve --nobrowser

3. In the list view, use the same query parameters used previously with the ID matching your extension identifier available in the HelloWorldCommandSet.manifest.json file.
4. Accept the loading of debug manifests by selecting Load debug scripts when prompted.

We still have the same buttons in the toolbar, but you'll notice they behave differently if you select them one-by-one. Now we are using the new Dialog API, which can be easily used
with your solutions, even for complex scenarios.

Add a ListView Command Set to a solution package for deployment


1. Return to your solution in Visual Studio Code (or to your preferred editor).
2. Extend the sharepoint folder and assets subfolder in the root of the solution to see the existing elements.xml file.
Review the elements.xml file
Open the elements.xml file inside the sharepoint\assets folder.
Note the following XML structure in elements.xml. The ClientSideComponentId property has been automatically updated to the unique ID of your ListView Command Set available in
the HelloWorldCommandSet.manifest.json file in the src\extensions\helloWorld folder.
Notice that we use a specific location value of ClientSideExtension.ListViewCommandSet.CommandBar to define that this is a ListView Command Set and it should be displayed in the command
bar. We also define the RegistrationId as 100 and the RegistrationType as List to associate this custom action automatically with generic lists. ClientSideComponentProperties can be used
to provide instance specific configurations. In this case, we are using default properties called sampleTextOne and sampleTextTwo.

<?xml version="1.0" encoding="utf-8"?>


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<CustomAction
Title="SPFxListViewCommandSet"
RegistrationId="100"
RegistrationType="List"
Location="ClientSideExtension.ListViewCommandSet.CommandBar"
ClientSideComponentId="5fc73e12-8085-4a4b-8743-f6d02ffe1240"
ClientSideComponentProperties="{&quot;sampleTextOne&quot;:&quot;One item is selected in the list.&quot;, &quot;sampleTextTwo&quot;:&quot;This command is always visible.&quot;}">
</CustomAction>

</Elements>

NOTE

While running from localhost the custom action will work on both lists and document libraries, but will not once deployed unless the elements.xml is updated. RegistrationId=100 will only
associate the custom action with lists and NOT document libraries. In order to associate the custom action with document libraries, the RegistrationId must be set to 101. If you would like
the action to work on both lists and document libraries, another CustomAction must be added to the elements.xml file

<?xml version="1.0" encoding="utf-8"?>


<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

<CustomAction
Title="SPFxListViewCommandSet"
RegistrationId="100"
RegistrationType="List"
Location="ClientSideExtension.ListViewCommandSet.CommandBar"
ClientSideComponentId="5fc73e12-8085-4a4b-8743-f6d02ffe1240"
ClientSideComponentProperties="{&quot;sampleTextOne&quot;:&quot;One item is selected in the list.&quot;, &quot;sampleTextTwo&quot;:&quot;This command is always visible.&quot;}">
</CustomAction>

<CustomAction
Title="SPFxListViewCommandSet"
RegistrationId="101"
RegistrationType="List"
Location="ClientSideExtension.ListViewCommandSet.CommandBar"
ClientSideComponentId="5fc73e12-8085-4a4b-8743-f6d02ffe1240"
ClientSideComponentProperties="{&quot;sampleTextOne&quot;:&quot;One item is selected in the list.&quot;, &quot;sampleTextTwo&quot;:&quot;This command is always visible.&quot;}">
</CustomAction>

</Elements>

Possible location values that can be used with a ListView Command Set:
ClientSideExtension.ListViewCommandSet.CommandBar - Toolbar of the list or library
ClientSideExtension.ListViewCommandSet.ContextMenu - Context menu for list or library items
ClientSideExtension.ListViewCommandSet - Register commands to both the toolbar and to the context menu

Ensure that definitions are taken into account within the build pipeline
Open package-solution.json from the config folder. The package-solution.json file defines the package metadata as shown in the following code. To ensure that the element.xml file is
taken into account while the solution is being packaged, default scaffolding added needed configuration to define a feature framework feature definition for the solution package.
{
"$schema": "https://dev.office.com/json-schemas/spfx-build/package-solution.schema.json",
"solution": {
"name": "command-extension-client-side-solution",
"id": "690ae189-a4fc-4b98-8f28-d4ec17448b7a",
"version": "1.0.0.0",
"features": [
{
"title": "Application Extension - Deployment of custom action.",
"description": "Deploys a custom action with ClientSideComponentId association",
"id": "e91d5532-3519-4b50-b55e-b142fc74cd8a",
"version": "1.0.0.0",
"assets": {
"elementManifests": [
"elements.xml"
]
}
}
]
},
"paths": {
"zippedPackage": "solution/command-extension.sppkg"
}
}

Deploy the extension to SharePoint Online and host JavaScript from local host
Now you are ready to deploy the solution to a SharePoint site and have the CustomAction automatically associated on the site level.
1. In the console window, enter the following command to package your client-side solution that contains the extension so that we get the basic structure ready for packaging:

gulp bundle

2. Execute the following command so that the solution package is created:

gulp package-solution

The command creates the package in the sharepoint/solution folder:

command-extension.sppkg

3. Deploy the package that was generated to the app catalog. To do this, go to your tenant's app catalog and open the Apps for SharePoint library.
4. Upload or drag-and-drop the command-extension.sppkg located in the sharepoint/solution folder to the app catalog. SharePoint displays a dialog and asks you to trust the client-side
solution.
Note that we did not update the URLs for hosting the solution for this deployment, so the URL is still pointing to https://localhost:4321 .
5. Select the Deploy button.

6. In your console, ensure that the solution is running. If it's not running, execute the following command in the solution folder:

gulp serve --nobrowser

7. Go to the site where you want to test SharePoint asset provisioning. This could be any site collection in the tenant where you deployed this solution package.
8. Select the gears icon on the top navigation bar on the right, and then select Add an app to go to your Apps page.
9. In the Search box, enter extension, and then select Enter to filter your apps.
10. Select the command-extension-client-side-solution app to install the solution on the site. When the installation is complete, refresh the page by selecting F5.
11. When the application has been successfully installed, select New from the toolbar on the Site Contents page, and then select List.

12. Provide the name as Sample, and then select Create.


Notice how Command One and Command Two are rendering in the toolbar based on your ListView Command Set customizations.

NOTE

If you find an issue in the documentation or in the SharePoint Framework, report that to SharePoint engineering by using the issue list at the sp-dev-docs repository. Thanks for your input
in advance.

See also
Build your first Field Customizer extension
Overview of SharePoint Framework Extensions
Configure extension icon
3/26/2018 • 2 minutes to read Edit Online

Selecting an icon that illustrates the purpose of your custom command in SharePoint Framework makes it easier for users to find your command among other options visible in the toolbar
or in the context menu. Specifying an icon for a command is optional. If you don't specify an icon, only the command title is displayed in the command bar.
SharePoint Framework supports building the following types of extensions:
Application Customizer
Field Customizer
Command Set
The Command Set is the only type of SharePoint Framework Extension for which you can configure icons.
When deploying Command Sets, you can choose whether their commands should be visible on:
The command bar ( location: ClientSideExtension.ListViewCommandSet.CommandBar )
The context menu ( location: ClientSideExtension.ListViewCommandSet.ContextMenu )
Both ( location: ClientSideExtension.ListViewCommandSet )

Icons defined for the different commands are displayed only for commands displayed in the command bar.
SharePoint Framework offers you two ways to define the icon for your extension:
Use an external icon image
Use a base64-encoded image

Use an external icon image


When building SharePoint Framework command sets, you can specify an icon for each command by providing an absolute URL pointing to the icon image in the extension manifest, in the
iconImageUrl property.

{
"$schema": "https://dev.office.com/json-schemas/spfx/command-set-extension-manifest.schema.json",

"id": "6cdfbff6-714f-4c26-a60c-0b18afe60837",
"alias": "WeatherCommandSet",
"componentType": "Extension",
"extensionType": "ListViewCommandSet",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,

"items": {
"WEATHER": {
"title": { "default": "Weather" },
"iconImageUrl": "https://localhost:4321/temp/sun.png",
"type": "command"
}
}
}

The command icon displayed in the command bar is 16x16 px. If your image is bigger, it is sized proportionally to match these dimensions.
While using custom images gives you flexibility to choose an icon for your command, it requires you to deploy them along with your other extension assets. Additionally, your image might
lose quality when displayed in higher DPI or specific accessibility settings. To avoid quality loss, you can use vector-based SVG images, which are also supported by the SharePoint
Framework.

Use a base64-encoded image


When using a custom image, rather than specifying an absolute URL to the image file hosted together with other extension assets, you can have your image base64-encoded and use the
base64 string instead of the URL.
A number of services are available online that you can use to base64-encode your image, such as Convert your images to Base64.
After encoding the image, copy the base64 string and use it as the value for the iconImageUrl property in the web part manifest.

{
"$schema": "https://dev.office.com/json-schemas/spfx/command-set-extension-manifest.schema.json",

"id": "6cdfbff6-714f-4c26-a60c-0b18afe60837",
"alias": "WeatherCommandSet",
"componentType": "Extension",
"extensionType": "ListViewCommandSet",

// The "*" signifies that the version should be taken from the package.json
"version": "*",
"manifestVersion": 2,

// If true, the component can only be installed on sites where Custom Script is allowed.
// Components that allow authors to embed arbitrary script code should set this to true.
// https://support.office.com/en-us/article/Turn-scripting-capabilities-on-or-off-1f2c515f-5d7e-448a-9fd7-835da935584f
"requiresCustomScript": false,

"items": {
"WEATHER": {
"title": { "default": "Weather" },
"iconImageUrl": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAAXNSR0IB2cksfwAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAB/hUlEQVR42u29ebwkWVUn/j03Ip
"type": "command"
}
}
}

Base64 encoding works for both bitmap images, such as PNG, as well as vector SVG images. The big benefit of using base64-encoded images is that you don't need to deploy the web part
icon image separately.
See also
Overview of SharePoint Framework Extensions
Migrating JSLink customizations to SharePoint Framework Field Customizers
3/26/2018 • 10 minutes to read Edit Online

SharePoint Framework is a new model for building SharePoint customizations. If you customized SharePoint fields and list views with JSLink, you might be wondering what the advantages
of migrating them to the new SharePoint Framework are.
First, let's introduce the available options when developing SharePoint Framework Extensions:
Application Customizer. Extend the native "modern" UI of SharePoint Online by adding custom HTML elements and client-side code to pre-defined placeholders of "modern" pages. At
the time of this writing, the available placeholders are the header and the footer of every "modern" page.
Command Set. Add custom ECB menu items or custom buttons to the command bar of a list view for a list or a library. You can associate any JavaScript (TypeScript) action to these
commands.
Field Customizer. Customize the rendering of a field in a list view by using custom HTML elements and client-side code.
Depending on what is the target of your customization, you can leverage any of the above flavors. For example, the Field Customizers are a good replacement for the JSLink customizations
of fields.

Benefits of migrating existing JSLink customizations to the SharePoint Framework


SharePoint Framework was built thinking about the new SharePoint "modern" experience, which is available on the "modern" team sites and "modern" communication sites, and which
targets any device and any platform.

The only supported way of customizing "modern" lists and libraries


One of the main benefits of migrating old-school JSLink customizations to the SharePoint Framework is that it is the only supported technique that you have on modern sites for
customizing the UI of "modern" lists and libraries. In fact, the "modern" lists and libraries, because of their rendering infrastructure and because of the no-script flag enabled on the "modern"
sites, can't rely on old-school JSLink customizations. Thus, if you really want to extend the "modern" UI, you need to upgrade to the SharePoint Framework.

Easier access to information in SharePoint and Office 365


Another fundamental topic to consider is that in the old-school JSLink customizations it was not easy to consume SharePoint content and data. The only way of doing that was by using
JSOM (the JavaScript client-side object model of SharePoint) or low-level REST APIs. Moreover, it was almost impossible to consume the full set of services of Office 365 unless you
autonomously leveraged ADAL.JS (Azure Active Directory Authentication Library for JavaScript) and custom JavaScript code.
Now, with the SharePoint Framework and the SharePoint Framework Extensions, it is easy and straightforward to consume both the SharePoint REST API and Microsoft Graph. You can
now create more powerful solutions, which can consume and interact with the full ecosystem of services provided by Microsoft Office 365.

Similarities between SharePoint Framework solutions and SharePoint Feature Framework customizations
Nevertheless, both the JSLink customizations and the SharePoint Feature Framework customizations share some similarities.

Provisioning model
Both SharePoint Framework Extensions and user custom actions or the Edit Control Block (ECB) menu item solutions leverage an XML manifest file, which is written with the SharePoint
Feature Framework syntax. Thus, the deployment is based on the same techniques. However, with the new Field Customizers, you can customize the rendering of a field, and not the
rendering of a single view of a list or library. Of course, the custom field can be used in as many lists and libraries as you like.

Run as a part of the page


Similar to user custom actions and ECB of SharePoint Feature Framework, SharePoint Framework Extensions are a part of the page. This gives these solutions access to the page's DOM and
allows them to communicate with other components on the same page. Also, it allows developers to more easily make their solutions responsive to adapt to the different form factors on
which a SharePoint page could be displayed, including the SharePoint mobile app.
Because SharePoint Framework Extensions run as part of the page, whatever resources the customization has access to, other elements on the page can access as well. This is important to
keep in mind when building solutions that rely on OAuth implicit flow with bearer access tokens or use cookies or browser storage for storing sensitive information. Because SharePoint
Framework Extensions run as a part of the page, other elements on that page can access all these resources as well.

Use any library to build your extensions


When building page customizations by using user custom actions, you might have used libraries such as jQuery or Knockout to make your customization dynamic and better respond to user
interaction. When building SharePoint Framework Extensions, you can use any client-side library to enrich your solution, the same way you would have done in the past.
An additional benefit that the SharePoint Framework offers you is isolation of your script. Even though the web part is executed as a part of the page, its script is packaged as a modul