@@ -38,14 +38,16 @@ yo @microsoft/sharepoint
38
38
Enter the following values when prompted during the setup of the new project:
39
39
40
40
- ** spfx-sp-pnp-js-example** as solution name (keep default)
41
+ - ** SharePoint Online only (latest)** as the baseline packages version
41
42
- ** Current Folder** as the solution ___location
42
- - ** Knockout** as the framework
43
+ - ** Y** as allow tenant admin to deploy solution to all sites
44
+ - ** WebPart** as component to create
43
45
- ** SPPnPJSExample** as the name of the web part
44
46
- ** Example of using sp-pnp-js within SPFx** as the description
47
+ - ** Knockout** as the framework
45
48
46
49
![ Completed Project Scaffolding] ( ../../../images/sp-pnp-js-guide-completed-setup.png )
47
50
48
-
49
51
Once the scaffolding completes, open the project in the code editor of your choosing. The screenshots shown here demonstrate [ Visual Studio Code] ( https://code.visualstudio.com/ ) . To open the directory within Visual Studio Code, enter the following in the console:
50
52
51
53
``` sh
@@ -115,8 +117,8 @@ The takeaway is that by using sp-pnp-js, we write much less code to handle reque
115
117
``` TypeScript
116
118
import * as ko from ' knockout' ;
117
119
import styles from ' ./SpPnPjsExample.module.scss' ;
118
- import { ISpPnPjsExampleWebPartProps } from ' ./ISpPnPjsExampleWebPartProps ' ;
119
- import pnp , { List , ListEnsureResult , ItemAddResult } from " sp-pnp-js" ;
120
+ import { ISpPnPjsExampleWebPartProps } from ' ./SpPnPjsExampleWebPart ' ;
121
+ import pnp , { List , ListEnsureResult , ItemAddResult , FieldAddResult } from " sp-pnp-js" ;
120
122
121
123
export interface ISpPnPjsExampleBindingContext extends ISpPnPjsExampleWebPartProps {
122
124
shouter: KnockoutSubscribable <{}>;
@@ -131,15 +133,16 @@ export interface OrderListItem {
131
133
OrderNumber: string ;
132
134
}
133
135
134
- export default class SpPnPjsExampleViewModel {
136
+ const LIST_EXISTS : string = ' List exists ' ;
135
137
138
+ export default class SpPnPjsExampleViewModel {
136
139
public description: KnockoutObservable <string > = ko .observable (' ' );
137
140
public newItemTitle: KnockoutObservable <string > = ko .observable (' ' );
138
141
public newItemNumber: KnockoutObservable <string > = ko .observable (' ' );
139
142
public items: KnockoutObservableArray <OrderListItem > = ko .observableArray ([]);
140
143
141
144
public labelClass: string = styles .label ;
142
- public helloWorldClass : string = styles .helloWorld ;
145
+ public spPnPjsExampleClass : string = styles .spPnPjsExample ;
143
146
public containerClass: string = styles .container ;
144
147
public rowClass: string = ` ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles .row } ` ;
145
148
public buttonClass: string = ` ms-Button ${styles .button } ` ;
@@ -153,8 +156,7 @@ export default class SpPnPjsExampleViewModel {
153
156
}, this , ' description' );
154
157
155
158
// Load the items
156
- this .getItems ().then (items => {
157
-
159
+ this .getItems ().then ((items : OrderListItem []): void => {
158
160
this .items (items );
159
161
});
160
162
}
@@ -163,9 +165,7 @@ export default class SpPnPjsExampleViewModel {
163
165
* Gets the items from the list
164
166
*/
165
167
private getItems(): Promise <OrderListItem []> {
166
-
167
- return this .ensureList ().then (list => {
168
-
168
+ return this .ensureList ().then ((list : List ): Promise <OrderListItem []> => {
169
169
// Here we are using the getAs operator so that our returned value will be typed
170
170
return list .items .select (" Id" , " Title" , " OrderNumber" ).getAs <OrderListItem []>();
171
171
});
@@ -175,28 +175,24 @@ export default class SpPnPjsExampleViewModel {
175
175
* Adds an item to the list
176
176
*/
177
177
public addItem(): void {
178
-
179
178
if (this .newItemTitle () !== " " && this .newItemNumber () !== " " ) {
180
-
181
- this .ensureList ().then (list => {
182
-
179
+ this .ensureList ().then ((list : List ): Promise <ItemAddResult > => {
183
180
// Add the new item to the SharePoint list
184
- list .items .add ({
181
+ return list .items .add ({
185
182
Title: this .newItemTitle (),
186
183
OrderNumber: this .newItemNumber (),
187
- }).then ((iar : ItemAddResult ) => {
188
-
189
- // Add the new item to the display
190
- this .items .push ({
191
- Id: iar .data .Id ,
192
- OrderNumber: iar .data .OrderNumber ,
193
- Title: iar .data .Title ,
194
- });
195
-
196
- // Clear the form
197
- this .newItemTitle (" " );
198
- this .newItemNumber (" " );
199
184
});
185
+ }).then ((iar : ItemAddResult ) => {
186
+ // Add the new item to the display
187
+ this .items .push ({
188
+ Id: iar .data .Id ,
189
+ OrderNumber: iar .data .OrderNumber ,
190
+ Title: iar .data .Title ,
191
+ });
192
+
193
+ // Clear the form
194
+ this .newItemTitle (" " );
195
+ this .newItemNumber (" " );
200
196
});
201
197
}
202
198
}
@@ -205,85 +201,84 @@ export default class SpPnPjsExampleViewModel {
205
201
* Deletes an item from the list
206
202
*/
207
203
public deleteItem(data ): void {
208
-
209
- if (confirm (" Are you sure you want to delete this item?" )) {
210
- this .ensureList ().then (list => {
211
- list .items .getById (data .Id ).delete ().then (_ => {
212
- this .items .remove (data );
213
- });
214
- }).catch ((e : Error ) => {
215
- alert (` There was an error deleting the item: ${e .message } ` );
216
- });
204
+ if (! confirm (" Are you sure you want to delete this item?" )) {
205
+ return ;
217
206
}
207
+
208
+ this .ensureList ().then ((list : List ): Promise <void > => {
209
+ return list .items .getById (data .Id ).delete ();
210
+ }).then (_ => {
211
+ this .items .remove (data );
212
+ }).catch ((e : Error ) => {
213
+ alert (` There was an error deleting the item: ${e .message } ` );
214
+ });
218
215
}
219
216
220
217
/**
221
218
* Ensures the list exists. If not, it creates it and adds some default example data
222
219
*/
223
220
private ensureList(): Promise <List > {
224
-
225
- return new Promise <List >((resolve , reject ) => {
226
-
221
+ return new Promise <List >((resolve : (list : List ) => void , reject : (err : string ) => void ): void => {
222
+ let listEnsureResults: ListEnsureResult ;
227
223
// Use lists.ensure to always have the list available
228
- pnp .sp .web .lists .ensure (" SPPnPJSExampleList" ).then ((ler : ListEnsureResult ) => {
224
+ pnp .sp .web .lists .ensure (" SPPnPJSExampleList" )
225
+ .then ((ler : ListEnsureResult ): Promise <FieldAddResult > => {
226
+ listEnsureResults = ler ;
229
227
230
- if (ler .created ) {
228
+ if (! ler .created ) {
229
+ // resolve main promise
230
+ resolve (ler .list );
231
+ // break promise chain
232
+ return Promise .reject (LIST_EXISTS );
233
+ }
231
234
232
235
// We created the list on this call, so let's add a column
233
- ler .list .fields .addText (" OrderNumber" ).then (_ => {
234
-
235
- // And we will also add a few items so we can see some example data
236
- // Here we use batching
237
-
238
- // Create a batch
239
- let batch = pnp .sp .web .createBatch ();
240
-
241
- ler .list .getListItemEntityTypeFullName ().then (typeName => {
242
-
243
- ler .list .items .inBatch (batch ).add ({
244
- Title: " Title 1" ,
245
- OrderNumber: " 4826492"
246
- }, typeName );
247
-
248
- ler .list .items .inBatch (batch ).add ({
249
- Title: " Title 2" ,
250
- OrderNumber: " 828475"
251
- }, typeName );
252
-
253
- ler .list .items .inBatch (batch ).add ({
254
- Title: " Title 3" ,
255
- OrderNumber: " 75638923"
256
- }, typeName );
257
-
258
- // Excute the batched operations
259
- batch .execute ().then (_ => {
260
- // All of the items have been added within the batch
261
-
262
- resolve (ler .list );
263
-
264
- }).catch (e => reject (e ));
265
-
266
- }).catch (e => reject (e ));
267
-
268
- }).catch (e => reject (e ));
269
-
270
- } else {
271
-
272
- resolve (ler .list );
273
- }
274
-
275
- }).catch (e => reject (e ));
236
+ return ler .list .fields .addText (" OrderNumber" );
237
+ }).then ((): Promise <string > => {
238
+ console .warn (' Adding items...' );
239
+ // And we will also add a few items so we can see some example data
240
+ // Here we use batching
241
+ return listEnsureResults .list .getListItemEntityTypeFullName ();
242
+ }).then ((typeName : string ): Promise <void > => {
243
+ // Create a batch
244
+ const batch = pnp .sp .web .createBatch ();
245
+ listEnsureResults .list .items .inBatch (batch ).add ({
246
+ Title: " Title 1" ,
247
+ OrderNumber: " 4826492"
248
+ }, typeName );
249
+
250
+ listEnsureResults .list .items .inBatch (batch ).add ({
251
+ Title: " Title 2" ,
252
+ OrderNumber: " 828475"
253
+ }, typeName );
254
+
255
+ listEnsureResults .list .items .inBatch (batch ).add ({
256
+ Title: " Title 3" ,
257
+ OrderNumber: " 75638923"
258
+ }, typeName );
259
+
260
+ // Execute the batched operations
261
+ return batch .execute ();
262
+ }).then ((): void => {
263
+ // All of the items have been added within the batch
264
+ resolve (listEnsureResults .list );
265
+ }).catch ((e : any ): void => {
266
+ if (e !== LIST_EXISTS ) {
267
+ reject (e );
268
+ }
269
+ });
276
270
});
277
271
}
278
272
}
279
273
```
274
+
280
275
## Update the Template
281
276
282
277
Finally, we need to update the template to match the functionality we have added into the ViewModel. Copy the code below into the ** SpPnPjsExample.template.html** file. We have added a title row, a foreach repeater
283
278
for the items collection, and a form allowing you to add new items to the list.
284
279
285
280
``` html
286
- <div data-bind =" attr: {class:helloWorldClass }" >
281
+ <div data-bind =" attr: {class:spPnPjsExampleClass }" >
287
282
<div data-bind =" attr: {class:containerClass}" >
288
283
289
284
<div data-bind =" attr: {class:rowClass}" >
@@ -339,6 +334,7 @@ for the items collection, and a form allowing you to add new items to the list.
339
334
</div >
340
335
</div >
341
336
```
337
+
342
338
## Run the Example
343
339
344
340
Start the sample and add the web part to your SharePoint hosted workbench (/_ layouts/workbench.aspx) to can see it in action.
@@ -359,7 +355,7 @@ The sp-pnp-js library contains a great range of functionality and extensibility.
359
355
360
356
When you are ready to deploy your solution and want to build using the ` --ship ` flag you need to mark sp-pnp-js as an external library in the configuration. This is done by updating the SPFx ** config/config.js** file to include this line in the externals section:
361
357
362
- ```
358
+ ``` json
363
359
"sp-pnp-js" : " https://cdnjs.cloudflare.com/ajax/libs/sp-pnp-js/2.0.1/pnp.min.js"
364
360
```
365
361
@@ -376,7 +372,7 @@ Add a new file named **MockSpPnPjsExampleViewModel.ts** alongside the other web
376
372
``` TypeScript
377
373
import * as ko from ' knockout' ;
378
374
import styles from ' ./SpPnPjsExample.module.scss' ;
379
- import { ISpPnPjsExampleWebPartProps } from ' ./ISpPnPjsExampleWebPartProps ' ;
375
+ import { ISpPnPjsExampleWebPartProps } from ' ./SpPnPjsExampleWebPart ' ;
380
376
import pnp , { List , ListEnsureResult , ItemAddResult } from " sp-pnp-js" ;
381
377
import { ISpPnPjsExampleBindingContext , OrderListItem } from ' ./SpPnPjsExampleViewModel' ;
382
378
@@ -388,7 +384,7 @@ export default class MockSpPnPjsExampleViewModel {
388
384
public items: KnockoutObservableArray <OrderListItem > = ko .observableArray ([]);
389
385
390
386
public labelClass: string = styles .label ;
391
- public helloWorldClass : string = styles .helloWorld ;
387
+ public spPnPjsExampleClass : string = styles .spPnPjsExample ;
392
388
public containerClass: string = styles .container ;
393
389
public rowClass: string = ` ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles .row } ` ;
394
390
public buttonClass: string = ` ms-Button ${styles .button } ` ;
@@ -397,13 +393,12 @@ export default class MockSpPnPjsExampleViewModel {
397
393
this .description (bindings .description );
398
394
399
395
// When the web part description is updated, change this view model's description.
400
- bindings .shouter .subscribe ((value : string ) => {
396
+ bindings .shouter .subscribe ((value : string ): void => {
401
397
this .description (value );
402
398
}, this , ' description' );
403
399
404
400
// Load the items
405
- this .getItems ().then (items => {
406
-
401
+ this .getItems ().then ((items : OrderListItem []): void => {
407
402
this .items (items );
408
403
});
409
404
}
@@ -433,9 +428,7 @@ export default class MockSpPnPjsExampleViewModel {
433
428
* Simulates adding an item to the list
434
429
*/
435
430
public addItem(): void {
436
-
437
431
if (this .newItemTitle () !== " " && this .newItemNumber () !== " " ) {
438
-
439
432
// Add the new item to the display
440
433
this .items .push ({
441
434
Id: this .items .length ,
@@ -453,47 +446,45 @@ export default class MockSpPnPjsExampleViewModel {
453
446
* Simulates deleting an item from the list
454
447
*/
455
448
public deleteItem(data ): void {
456
-
457
449
if (confirm (" Are you sure you want to delete this item?" )) {
458
450
this .items .remove (data );
459
451
}
460
452
}
461
453
}
462
454
```
455
+
463
456
### Update Webpart
464
457
465
458
Finally, we need to update the webpart to use the mock data when appropriate. Open the ** SpPnPjsExampleWebPart.ts** file. Start by importing the mock ViewModel web just created:
466
459
467
460
``` TypeScript
468
461
import MockSpPnPjsExampleViewModel from ' ./MockSpPnPjsExampleViewModel' ;
469
462
```
463
+
464
+ Next, import the ` Environment ` and ` EnvironmentType ` types that you will use to detect the type of
465
+ environment the web part is running in:
466
+
467
+ ``` ts
468
+ import { Environment , EnvironmentType } from ' @microsoft/sp-core-library' ;
469
+ ```
470
+
470
471
Then, locate the ` _registerComponent ` method and update it as shown below:
471
472
472
473
``` TypeScript
473
474
private _registerComponent (tagName : string ): void {
474
-
475
- if (Environment .type === EnvironmentType .Local ) {
476
- console .log (" here I am." )
477
- ko .components .register (
478
- tagName ,
479
- {
480
- viewModel: MockSpPnPjsExampleViewModel ,
481
- template: require (' ./SpPnPjsExample.template.html' ),
482
- synchronous: false
483
- }
484
- );
485
- } else {
486
- ko .components .register (
487
- tagName ,
488
- {
489
- viewModel: SpPnPjsExampleViewModel ,
490
- template: require (' ./SpPnPjsExample.template.html' ),
491
- synchronous: false
492
- }
493
- );
494
- }
475
+ ko .components .register (
476
+ tagName ,
477
+ {
478
+ viewModel: Environment .type === EnvironmentType .Local ?
479
+ MockSpPnPjsExampleViewModel :
480
+ SpPnPjsExampleViewModel ,
481
+ template: require (' ./SpPnPjsExample.template.html' ),
482
+ synchronous: false
483
+ }
484
+ );
495
485
}
496
486
```
487
+
497
488
Finally, type ` gulp serve ` in the console to bring up the local workbench, which now will work with the mock data. (If you already have the server running, stop it using Ctrl+C and then restart it):
498
489
499
490
``` sh
@@ -502,7 +493,6 @@ gulp serve
502
493
503
494
![ Project as it appears running in the local workbench with mock data] ( ../../../images/sp-pnp-js-guide-with-mock-data.png )
504
495
505
-
506
496
## Download Full Example Code
507
497
508
498
Remember you can find the full sample [ here] ( https://github.com/SharePoint/sp-dev-fx-webparts/tree/master/samples/knockout-sp-pnp-js ) .
0 commit comments