In this article, we will create a SPFx extension of type Command Sets which will generate QR code for selected item url. Below is the output you can expect:
Pre-requisites
- Create a SPFx extension command set using this link, this link has step by step approach on How to build the first Simple command set.
Once you have successfully created an SPFx extension using the above link, follow the below steps to change command set logic to implement QR code generator for the selected document.
The first thing we will do is install the required npm package which can be used to generate QR code.
- npm install --save qrcode
Above is the output of package updating, but you should see similar output.
CustomDailog.ts
The next thing to do here is create CustomDailog.ts, if you wanted to know how to create a custom dialog in SPFx extension, you can follow my
article where I have explained how to create custom dialogs and how to pass back and forth parameters to custom dialog and extension.
Below is code for CustomDailog.ts, create this file in the extension folder.
- import { BaseDialog, IDialogConfiguration } from '@microsoft/sp-dialog';
- import { SPComponentLoader } from '@microsoft/sp-loader';
- import styles from './DocumentQrCodeExtensionCommandSet.module.scss';
-
- import Download from './download';
-
- interface ICustomDialogContentProps {
- itemUrl: string;
- close: () => void;
- submit: (color: string) => void;
-
- }
- export default class CustomDailog extends BaseDialog {
- public itemUrl: string;
- public base64Image: string;
- public filename:string;
- public render(): void {
- this.domElement.innerHTML += `<div id="popup1" class="${ styles.mainpopup }">
- <div class="${ styles.popup }">
- <h2>QR Code</h2>
- <div class="${ styles.content }"> ` +
- `<img src="` + this.base64Image + `">
- </br>
- <button id="downloadQR">Download QR Code</button>
- <button id="close">Close</button>
- </div>
- </div>
- </div>`;
- this._setButtonEventHandlers();
- }
-
-
- private _setButtonEventHandlers(): void {
- const webPart: CustomDailog = this;
- this.domElement.querySelector('#downloadQR').addEventListener('click', () => {
- Download(this.base64Image, this.filename, "image/png");
- });
- this.domElement.querySelector('#close').addEventListener('click', () => {
- this.close();
- });
- }
- public getConfig(): IDialogConfiguration {
- return {
- isBlocking: false
- };
- }
-
- protected onAfterClose(): void {
- super.onAfterClose();
- }
- }
-
Now let us create scss file, which is being referred in CustomDailog.ts
SCSS file
Create a file named '<yourextensioname>.module.scss' , mine is 'DocumentQrCodeExtensionCommandSet.module.scss'
- .overlay {
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- background: rgba(0, 0, 0, 0.7);
- transition: opacity 500ms;
- opacity: 0;
- }
- .overlay:target {
- visibility: visible;
- opacity: 1;
- }
- .mainpopup {
- text-align: center;
- }
- .popup {
- padding: 20px;
- background: #fff;
- border-radius: 5px;
- position: relative;
- transition: all 5s ease-in-out;
-
- .h2 {
- margin-top: 0;
- color: #333;
- font-family: Tahoma, Arial, sans-serif;
- }
- .content {
- overflow: auto;
- }
- }
ExtensionCommandSet.ts
Now, go to your extensioncommanset.ts file, mine is 'DocumentQrCodeExtensionCommandSet.ts'
Import CustomDialog
- import CustomDailog from './CustomDialog';
Replace your onListViewUpdated method with below, here we are writing a condition that command should be visible only when one row is selected.
- @override
- public onListViewUpdated(event: IListViewCommandSetListViewUpdatedParameters): void {
- const compareOneCommand: Command = this.tryGetCommand('COMMAND_1');
- if (compareOneCommand) {
-
- compareOneCommand.visible = event.selectedRows.length === 1;
- }
- }
Replace your Execute method with below
- @override
- public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
- switch (event.itemId) {
- case 'COMMAND_1':
- var docurl = window.location.origin + event.selectedRows[0].getValueByName("FileRef");
-
- QRCode.toDataURL(docurl)
- .then(url => {
- console.log(url)
- const dialog: CustomDailog = new CustomDailog();
- dialog.itemUrl = event.selectedRows[0].getValueByName("FileRef");
- dialog.base64Image = url;
- dialog.filename = event.selectedRows[0].getValueByName("FileName");
- dialog.show().then(() => {
- });
- })
- .catch(err => {
- console.error(err)
- })
- break;
- default:
- throw new Error('Unknown command');
- }
- }
Download.ts
Next thing we will be doing is creating a typescript file which will provide functionality of Download QR code image. Basically, I did a little trick here, converted download.js to ts file for our use. We are using below library for download functionality.
http://danml.com/download.html
Create download.ts file in the same folder. Add the below code.
-
-
-
-
-
-
- function download(data, strFileName, strMimeType) {
-
- var self = (window as any),
- u = "application/octet-stream",
- m = strMimeType || u,
- x = data,
- D = document,
- a = D.createElement("a"),
- z = function(a){return String(a);},
-
-
- B = self.Blob || self.MozBlob || self.WebKitBlob || z,
- BB = self.MSBlobBuilder || self.WebKitBlobBuilder || self.BlobBuilder,
- fn = strFileName || "download",
- blob,
- b,
- ua,
- fr;
-
-
-
- if(String(this)==="true"){
- x=[x, m];
- m=x[0];
- x=x[1];
- }
-
- if(String(x).match(/^data\:[\w+\-]+\/[\w+\-]+[,;]/)){
- return navigator.msSaveBlob ?
- navigator.msSaveBlob(d2b(x), fn) :
- saver(x) ;
- }
-
- try{
-
- blob = x instanceof B ?
- x :
- new B([x], {type: m}) ;
- }catch(y){
- if(BB){
- b = new BB();
- b.append([x]);
- blob = b.getBlob(m);
- }
- }
- function d2b(u) {
- var p= u.split(/[:;,]/),
- t= p[1],
- dec= p[2] == "base64" ? atob : decodeURIComponent,
- bin= dec(p.pop()),
- mx= bin.length,
- i= 0,
- uia= new Uint8Array(mx);
-
- for(i;i<mx;++i) uia[i]= bin.charCodeAt(i);
-
- return new B([uia], {type: t});
- }
-
- function saver(url, winMode = false){
- if ('download' in a) {
- a.href = url;
- a.setAttribute("download", fn);
- a.innerHTML = "downloading...";
- D.body.appendChild(a);
- setTimeout(function() {
- a.click();
- D.body.removeChild(a);
- if(winMode===true){setTimeout(function(){ self.URL.revokeObjectURL(a.href);}, 250 );}
- }, 66);
- return true;
- }
-
- var f = D.createElement("iframe");
- D.body.appendChild(f);
- if(!winMode){
- url="data:"+url.replace(/^data:([\w\/\-\+]+)/, u);
- }
- f.src = url;
- setTimeout(function(){ D.body.removeChild(f); }, 333);
- }
- if (navigator.msSaveBlob) {
- return navigator.msSaveBlob(blob, fn);
- }
- if(self.URL){
- saver(self.URL.createObjectURL(blob), true);
- }else{
-
- if(typeof blob === "string" || blob.constructor===z ){
- try{
- return saver( "data:" + m + ";base64," + self.btoa(blob) );
- }catch(y){
- return saver( "data:" + m + "," + encodeURIComponent(blob) );
- }
- }
-
- fr=new FileReader();
- fr.onload=function(e){
- saver(this.result);
- };
- fr.readAsDataURL(blob);
- }
- return true;
- }
- export default download;
Now let us add icon and rename command text. Go to your extension.manifest.json file. Mine is 'DocumentQrCodeExtensionCommandSet.manifest.json'.
We would be using the SVG icon as taken from
sample by
Hugo Bernier . Here he has created similar functionality using the react framework.
- "items": {
- "COMMAND_1": {
- "title": { "default": "QR Code" },
- "iconImageUrl": "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='32' height='32' viewBox='0 0 401.994 401.994' style='enable-background:new 0 0 401.994 401.994;' xml:space='preserve' class='qrcodecommandsetcustomicon' %3E %3Cg%3E %3Cg%3E %3Cpath d='M0,401.991h182.724V219.265H0V401.991z M36.542,255.813h109.636v109.352H36.542V255.813z'/%3E %3Crect x='73.089' y='292.355' width='36.544' height='36.549'/%3E %3Crect x='292.352' y='365.449' width='36.553' height='36.545'/%3E %3Crect x='365.442' y='365.449' width='36.552' height='36.545'/%3E %3Cpolygon points='365.446,255.813 328.904,255.813 328.904,219.265 219.265,219.265 219.265,401.991 255.813,401.991 255.813,292.355 292.352,292.355 292.352,328.904 401.991,328.904 401.991,219.265 401.991,219.265 365.446,219.265 '/%3E %3Cpath d='M0,182.728h182.724V0H0V182.728z M36.542,36.542h109.636v109.636H36.542V36.542z'/%3E %3Crect x='73.089' y='73.089' width='36.544' height='36.547'/%3E %3Cpath d='M219.265,0v182.728h182.729V0H219.265z M365.446,146.178H255.813V36.542h109.633V146.178z'/%3E %3Crect x='292.352' y='73.089' width='36.553' height='36.547'/%3E %3C/g%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3Cg%3E %3C/g%3E %3C/svg%3E",
- "type": "command"
- }
- }
That's it, we have added all our files, below is how extension folder should look.
The next step to test this extension without deploying. Go to ./Config/serve.json
Change pageUrl properties to point to any View of document library of your SharePoint online site.,
- {
- "$schema": "https://developer.microsoft.com/json-schemas/core-build/serve.schema.json",
- "port": 4321,
- "https": true,
- "serveConfigurations": {
- "default": {
- "pageUrl": "https://warnerbros.sharepoint.com/sites/joker/mydocs/Forms/AllItems.aspx",
- "customActions": {
- "323203d-5f80-46232-aed3-2c0342338": {
- "location": "ClientSideExtension.ListViewCommandSet.CommandBar",
- "properties": {
- "sampleTextOne": "One item is selected in the list",
- "sampleTextTwo": "This command is always visible."
- }
- }
- }
- }
- }
- }
Once you are done with the above changes, go to Node JS command prompt and run 'gulp serve', It would open url mentioned in the above pageurl property. You should be able to see the below output. You can use the download button, which will download a png file that can be shared.
In this article, we have learned or implemented the below concepts:
- Creating SPFx command set Extension
- Using a QrCode npm package to generate QR code image
- Creating CustomDailog box for an extension to show custom html, pass parameters between extension and Dialog.
- Converting download.js library to typescript library
Complete code can be found at this
github repo. I hope you enjoyed reading, feel free to comment with any queries.