Using SP.Modal dialog in SPFX command set - reactjs

My requirement is to open a SharePoint page in a modal dialogue using command set in a list. I have followed this:
MSDN tutorial to create command set
and this question:
How to refernence sp.js
This is my .ts file code
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';
import { SPComponentLoader } from '#microsoft/sp-loader';
import * as strings from 'DocManagerCommandSetStrings';
require('sp-init');
require('microsoft-ajax');
require('sp-runtime');
require('sharepoint');
/**
* If your command set uses the ClientSideComponentProperties JSON input,
* it will be deserialized into the BaseExtension.properties object.
* You can define an interface to describe it.
*/
export interface IDocManagerCommandSetProperties {
// This is an example; replace with your own properties
sampleTextOne: string;
sampleTextTwo: string;
}
const LOG_SOURCE: string = 'DocManagerCommandSet';
export default class DocManagerCommandSet extends BaseListViewCommandSet<IDocManagerCommandSetProperties> {
#override
public onInit(): Promise<void> {
Log.info(LOG_SOURCE, 'Initialized DocManagerCommandSet');
return Promise.resolve();
}
#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;
}
}
#override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`${this.properties.sampleTextOne}`);
break;
case 'COMMAND_2':
//DocManagerCommandSet._loadSPJSOMScripts();
var options = {
title: "My Dialog Title",
width: 400,
height: 600,
url: "/_layouts/DialogPage.aspx" };
var value = SP.UI.ModalDialog.showModalDialog(options);
// Dialog.alert(`${this.properties.sampleTextTwo}`);
break;
default:
throw new Error('Unknown command');
}
}
private static getSiteCollectionUrl(): string {
let baseUrl = window.location.protocol + "//" + window.location.host;
const pathname = window.location.pathname;
const siteCollectionDetector = "/sites/";
if (pathname.indexOf(siteCollectionDetector) >= 0) {
baseUrl += pathname.substring(0, pathname.indexOf("/", siteCollectionDetector.length));
}
return baseUrl;
}
private static _loadSPJSOMScripts() {
const siteColUrl = "https://shelldevelopment.sharepoint.com/sites/SPODA0332/";
SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/init.js', {
globalExportsName: '$_global_init'
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/MicrosoftAjax.js', {
globalExportsName: 'Sys'
});
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.Runtime.js', {
globalExportsName: 'SP'
});
})
.then((): Promise<{}> => {
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/SP.js', {
globalExportsName: 'SP'
});
}) .then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/sp.init.js', {
globalExportsName: 'SP'
});
}).then((): Promise<{}> => {
return SPComponentLoader.loadScript('/_layouts/15/sp.ui.dialog.js', {
globalExportsName: 'SP'
});
});
}
}
I am getting the following error:
cannot find the name 'SP'.in the line
SP.UI.ModalDialog.showModalDialog(options)
Kindly provide some insights as I am a beginner in SPFX

Theoretically you need to uncomment //DocManagerCommandSet._loadSPJSOMScripts(); and wait for the promise to return.
Update the loadSPJSOMScripts message to return the promise:
private static _loadSPJSOMScripts(): Promise<void> {
const siteColUrl = "https://shelldevelopment.sharepoint.com/sites/SPODA0332/";
return SPComponentLoader.loadScript(siteColUrl + '/_layouts/15/init.js', {
globalExportsName: '$_global_init'
})
// [the rest of the calls... ]
.then(_ => {});
}
to load in the onInit():
public onInit(): Promise<void> {
return Promise.resolve()
.then(_ => {
return DocManagerCommandSet._loadSPJSOMScripts();
});
}
Or in your onExecute:
#override
public onExecute(event: IListViewCommandSetExecuteEventParameters): void {
let launchModal = false;
switch (event.itemId) {
case 'COMMAND_1':
Dialog.alert(`${this.properties.sampleTextOne}`);
break;
case 'COMMAND_2':
launchModal = true;
break;
// ...
}
if (launchModal) {
DocManagerCommandSet._loadSPJSOMScripts()
.then(_ => {
var options = {
title: "My Dialog Title",
width: 400,
height: 600,
url: "/_layouts/DialogPage.aspx"
};
var value = SP.UI.ModalDialog.showModalDialog(options);
});
}
}
That being said, there may be better ways to work with JSOM in SPFX.

Related

How to implement Authorization with Custom Directives in apollo with graphql-tools/utils?

I know that Apollo 2 allowed custom directives by extending the class "SchemaDirectiveVisitor." However, I am using apollo 3 and I know that the way to achieve this now is by using graphql-tools/utils and graphql-tools/schema.
In my index.js I have the following code:
const serverServer = async () => {
app.use(AuthMiddleware);
app.use(
cors({
origin: 'mydomain',
})
);
let schema = makeExecutableSchema({
typeDefs: [typeDefsLibrary, typeDefsDynamicContent, userTypeDefs],
resolvers: {
Query,
Mutation,
Article,
Blog,
Podcast,
SermonNotes,
Sermon,
// dynamic Content
Friday,
Thursday,
// Post Content
Commentary,
Quote,
Thought,
UserContent_SermonNotes,
// User Content
User,
All_Posts,
},
});
schema = AuthorizationDirective(schema, 'auth');
const apolloServer = new ApolloServer({
schema,
context: ({ req }) => {
const { isAuth, user } = req;
return {
req,
isAuth,
user,
};
},
});
await apolloServer.start();
apolloServer.applyMiddleware({ app: app, path: '/api' });
app.listen(process.env.PORT, () => {
console.log(`listening on port 4000`);
});
};
serverServer();
then on my schema file I have:
directive #auth(requires: [RoleName] ) on OBJECT | FIELD_DEFINITION
enum RoleName {
SUPERADMIN
ADMIN
}
type Commentary #auth(requires: [SUPERADMIN, ADMIN]) {
ID: ID
USER_ID: ID
VERSE_ID: String
body: String
category_tags: String
referenced_verses: String
verse_citation: String
created_date: String
posted_on: String
creator(avatarOnly: Boolean): User
comments(showComment: Boolean): [Commentary_Comment]
approvals: [Commentary_Approval]
total_count: Int
}
and this is my custom directive code:
const { mapSchema, getDirective, MapperKind } = require('#graphql-tools/utils');
const { defaultFieldResolver } = require('graphql');
const { ApolloError } = require('apollo-server-express');
//const { logging } = require('../../helpers');
module.exports.AuthorizationDirective = (schema, directiveName) => {
return mapSchema(schema, {
[MapperKind.FIELD]: (fieldConfig, _fieldName, typeName) => {
const authDirective = getDirective(schema, fieldConfig, directiveName);
console.log('auth Directive line 10: ', authDirective);
if (authDirective && authDirective.length) {
const requiredRoles = authDirective[0].requires;
if (requiredRoles && requiredRoles.length) {
const { resolve = defaultFieldResolver } = fieldConfig;
fieldConfig.resolve = function (source, args, context, info) {
if (requiredRoles.includes('PUBLIC')) {
console.log(
`==> ${context.code || 'ANONYMOUS'} ACCESSING PUBLIC RESOLVER: ${
info.fieldName
}`
);
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
}
if (!requiredRoles.includes(context.code)) {
throw new ApolloError('NOT AUTHORIZED', 'NO_AUTH');
}
console.log(`==> ${context.code} ACCESSING PRIVATE RESOLVER: ${info.fieldName}`);
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
},
});
};
But is not working. It seems like it is not even calling the Custom Directive. As you see I have a "console.log('auth Directive line 10: ', authDirective);" on my schema directive function that return "undefined."
I know this post is so ling but I hope someone can help!
Thanks in advance!
Below is the code worked for me
I have used [MapperKind.OBJECT_FIELD]: not [MapperKind.FIELD]:
I have referred this from #graphql-tools ->
https://www.graphql-tools.com/docs/schema-directives#enforcing-access-permissions
`
const { mapSchema, getDirective, MapperKind } = require('#graphql-tools/utils');
const { defaultFieldResolver } = require('graphql');
const HasRoleDirective = (schema, directiveName) => {
return mapSchema(schema, {
// Executes once for each object field in the schems
[MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
// Check whether this field has the specified directive
const authDirective = getDirective(schema, fieldConfig, directiveName);
if (authDirective && authDirective.length) {
const requiredRoles = authDirective[0].requires;
// console.log("requiredRoles: ", requiredRoles);
if (requiredRoles && requiredRoles.length) {
// Get this field's original resolver
const { resolve = defaultFieldResolver } = fieldConfig;
// Replace the original resolver with function that "first" calls
fieldConfig.resolve = function (source, args, context, info) {
// console.log("Context Directive: ", context);
const { currentUser } = context;
if(!currentUser) throw new Error("Not Authenticated");
const { type } = currentUser['userInfo']
const isAuthorized = hasRole(type, requiredRoles);
if(!isAuthorized) throw new Error("You Have Not Enough Permissions!")
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
}
})
}
`

NEXT.js Sudden error - Refused to execute script from worker.ts because it's MIME type (video/mp2t) is not executable

I was writing code - plugging around with webworkers. It was literally working perfectly - worker running, hit save, and now Next.js considers the file a (video/mp2t)...wat?
I L.I.T.E.R.A.L.L.Y just hit save. No server reboot, config change no nothing. It was working.
Here's the worker if anyone smarter than me can identify some magic phrase or something idk.
I really am stuck here, please help.
/* eslint-disable no-return-assign */
/* eslint-disable no-param-reassign */
/* eslint-disable no-restricted-globals */
import { nanoid } from 'nanoid';
export interface FileObj {
file: File;
path: string;
relativePath?: string;
folder: string;
id: string;
}
type Files = FileObj[];
export const createQueue = (files: Files) => {
const obj: any = {};
const pathIds: any = {};
files.forEach(({ path, ...rest }: { path: string }) => {
const split = path.split('/').filter((f: string) => f);
split.shift();
const relativePath = split.join('/');
if (!pathIds[split[0]]) pathIds[split[0]] = nanoid();
if (!obj[pathIds[split[0]]]) obj[pathIds[split[0]]] = [];
obj[pathIds[split[0]]].push({
path,
folder: split[0],
relativePath,
id: pathIds[split[0]],
...rest,
});
});
return obj;
};
interface Queue {
[key: string]: FileObj[];
}
const parseQueue = (queue: Queue) => Object.entries(queue).map(([key, value]) => {
const files: any[] = [];
const objectType = value.length > 1 ? 'folder' : 'file';
let entityName = '';
let cumulativeSize = 0;
value.forEach((fileObj: FileObj) => {
const {
file: { name, size, type },
relativePath,
folder,
path,
} = fileObj;
files.push({
name,
size,
folder,
type,
relativePath,
path,
});
cumulativeSize += size;
entityName = folder;
});
return {
id: key,
objectType,
cumulativeSize,
files,
entityName,
};
});
function zip(folderObject: { files: Files; id: string }) {
// console.debug('zip files', files);
const zipper = new Worker(
new URL('../workers/zipper.worker.ts', import.meta.url),
);
zipper.onmessage = (evt: any) => {
console.debug('bundler zip', evt.data);
if (evt.data.type === 'result') {
postMessage(evt.data, [evt.data.payload.buffer]);
} else {
postMessage(evt.data);
}
if (evt.data.type === 'result') {
fetch(evt.data.payload).then((b) => console.debug(b.blob()));
zipper.terminate();
fetch(evt.data.payload).then((b) => console.debug(b.blob()));
}
};
zipper.postMessage(folderObject);
}
function gzip(file: FileObj) {
const gzipper = new Worker(
new URL('../workers/gzipper.worker.ts', import.meta.url),
);
gzipper.onmessage = (evt: any) => {
console.debug('bundler gzip', evt.data);
// postMessage(evt.data, [evt.data.payload.buffer]);
gzipper.terminate();
};
gzipper.postMessage(file);
}
function delegate(id: string, files: Files) {
console.debug({ id, files });
if (files.length > 1) {
zip({ files, id });
} else if (files.length === 1) {
gzip({ ...files[0], id });
}
}
addEventListener('message', (event: MessageEvent) => {
console.debug('gay');
const queue = createQueue(event.data);
postMessage({ payload: parseQueue(queue), type: 'queue', source: 'bundler' });
Object.entries(queue).forEach(([id, files]: any) => {
delegate(id, files);
});
});
export {};

Why do I keep getting error while pushing data to Array in Angular 10?

I'm trying to create Angular Material Chips as shown on the site, but I keep getting this error about the array being null.
Here's the component
import { Component, Input, OnInit } from '#angular/core';
import { ActivatedRoute } from '#angular/router';
import { IStatement } from 'src/Interface/ICorporateStatement';
import { StatementService } from '../services/statement.service';
import { MatChipInputEvent } from '#angular/material/chips';
import {COMMA, ENTER} from '#angular/cdk/keycodes';
export interface Tag{
corporate_statement_link_id: number;
name: string;
}
#Component({
selector: 'app-statement-detail',
templateUrl: './statement-detail.component.html',
styleUrls: ['./statement-detail.component.css']
})
export class StatementDetailComponent implements OnInit {
statement: IStatement;
id: number;
tags: Tag[] = [];
visible = true;
selectable = true;
removable = true;
addOnBlur = true;
readonly separatorKeysCodes: number[] = [ENTER, COMMA];
constructor(private statementService: StatementService,
private router:ActivatedRoute) { }
ngOnInit(): void {
this.tags = [
{ corporate_statement_link_id: 1, name: 'EDI'}
];
console.log("Tags: ", this.tags);
this.router.queryParams.subscribe(param => {
this.id = param.id;
this.getStatement(this.id);
});
}
addTag(event: MatChipInputEvent): void {
console.log(this.tags);
const input = event.input;
const value = event.value;
console.log("Input: ", input);
console.log("Value: ", value);
console.log("Tags: ", this.tags);
this.tags.push({corporate_statement_link_id: this.statement.corporate_statement_link_id, name: value.trim()});
// // Add our fruit
// if ((value || '').trim()) {
// this.fruits.push({name: value.trim()});
// }
// // Reset the input value
// if (input) {
// input.value = '';
// }
}
removeTag(tag: Tag): void {
console.log("removing");
// const index = this.fruits.indexOf(fruit);
// if (index >= 0) {
// this.fruits.splice(index, 1);
// }
}
// get statement
getStatement(id){
this.statementService.getStatement(id).subscribe(data => {
this.statement = <IStatement>data[0];
//get tags
this.statementService.getTags(this.statement.corporate_statement_link_id)
.subscribe(tag => {
this.tags = <Tag[]>tag;
})
}, error => {
console.log(error);
});
}
}
I've refactored the code and moved things here and there, but still can't figure out why the array is still null.
It looks like this code is setting this.tags to null.
.subscribe(tag => {
this.tags = <Tag[]>tag;
})
It may be an issue with your <Tag[]> cast, or maybe the data coming back is null?
if it is expected you could replace any null value here with an empty array like this:
.subscribe(tag => {
this.tags = <Tag[]>tag || [];
})
and see if that helps.

Why is my object type not getting updated?

I'm creating a permission service using react typescript and I ran into the following problem. I have the class:
import {IPermission} from "../interfaces/IPermission";
class PermissionService {
private permissions: IPermission[] = [];
constructor(permissions: IPermission[]) {
this.permissions = permissions;
}
public getValue(key: string): IPermission['value'] {
const perm = this.permissions.find(permission => permission.key === key);
if (!perm) {
throw new Error('Could not find the permission');
}
return perm.value;
}
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms = [...this.permissions];
for (let i = 0; i < perms.length; i++) {
perms[i].defaultValue = defaultValue;
perms[i].value = value
}
this.permissions = perms;
console.log(perms);
}
public parseActivePermissions(permissions: IPermission[]): IPermission[] {
this.permissions.forEach(permission => {
permissions.forEach(activePermission => {
if (permission.key === activePermission.key) {
permission.defaultValue = activePermission.defaultValue;
permission.value = activePermission.value;
}
})
})
return this.permissions;
}
public getAll(): IPermission[] {
return this.permissions;
}
}
export default PermissionService;
and an AdminPermissions data file
import PermissionService from "../services/permission.service";
import {IPermission} from "../interfaces/IPermission";
import Permissions from "./Permissions";
const service: PermissionService = new PermissionService(Permissions);
service.modifyPermission('canAccessAcp', true, true);
const AdminPermissions: IPermission[] = service.getAll();
export default AdminPermissions;
The problem is, the service.modifyPermission() does not update the defaultValue and value of the permission. It's still false when console logging. Why is that?
UPDATE #1
Changed the file a bit. Still doesn't work. Now I'm directly changing the values, but they still log as false.
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
service.permissions.forEach(permission => {
if (permission.key === 'canAccessAcp') {
permission.defaultValue = true;
permission.value = true;
}
})
return service.permissions;
}
}
The problem is that with forEach you are not changing the actual value of each items, so you should do something like this:
class AdminPermissions {
public getAll(): IPermission[] {
const service: PermissionService = new PermissionService(Permissions);
return service.permissions.map(permission => {
if (permission.key === 'canAccessAcp') {
return (
{
...permission,
defaultValue: true,
value: true
}
)
}
return permission
});
}
}
I found a solution.
In the permission.service.ts
public modifyPermission(key: string, defaultValue: any, value: any): void {
const perms: IPermission[] = this.permissions.map(permission => {
if (permission.key === key) {
console.log('found permission');
return {
...permission,
defaultValue,
value
}
}
return permission
})
this.permissions = perms;
}

public variable becomes undefined in the function in typescript

My code is as shown below:
/// <reference path="../../../typings/app.d.ts" />
/// <reference path="../../../typings/tsd.d.ts" />
module App.Controller {
import Services = Core.Services;
import Shared = Core.Shared;
export class RestaurentInfoController extends BaseController {
public restaurentName: any = [];
public checkBox: any;
public restaurent: any;
public foodTruckList: any = [];
public foodCategories: any = [];
public drinkCategories: any = [];
public restaurentId : any;
static $inject: Array<string> = ['baseAppService', 'userAuthorizationService', 'storageService', 'eventService',];
constructor(
appService: Services.BaseAppService
, public userAuthorizationService: Services.UserAuthorizationService,
public storageService: Services.StorageService,
public eventService: Services.AppEventBusService) {
super(appService);
this.getRestaurentList();
}
routeTo(view) {
this.appService.routerService.routeToPage(view);
}
getRestaurentList = (): void => {
this.appService.networkService.get<any>(this.appService.appConstant.appUrls.getFoodTruckName).then((response) => {
this.foodTruckList = response.data;
},
(error) => { });
}
changeStatus = (): void => {
if (this.checkBox === '1') {
this.getFoodCategories();
}
else if (this.checkBox === '2') {
this.getDrinkCategories();
}
}
getFoodCategories = (): void => {
console.log("rest " + this.restaurent);
angular.forEach(this.foodTruckList, function (item) {
console.log("here" + item.foodtruck_name);
if(item.foodtruck_name === 'world in a box') {
console.log("match res "+ this.restaurent + " " + item._id);
this.restaurentId = item._id;
console.log("ressss "+ this.restaurentId);
}
});
console.log("restaurentId "+this.restaurentId);
this.appService.networkService.get<any>(`${this.appService.appConstant.appUrls.getFoodCategories}/${this.restaurentId}`).then((response) => {
this.foodCategories = response.data;
console.log('popuar Items Loaded', this.foodCategories);
},
(error) => { });
}
getDrinkCategories = (): void => {
var data = {
_id: this.restaurent._id
}
this.appService.networkService.get<any>(this.appService.appConstant.appUrls.getFoodTruckName, data).then((response) => {
this.foodTruckList = response.data;
console.log('popuar Items Loaded', this.foodTruckList);
},
(error) => { });
}
}
}
Here what happens is this.restaurentId is showing value for console.log with ressss .But somehow , the value becomes undefined when console.log with restaurentId is printed. What should I do to make it work?
When you use function() {} for callbacks, the context (this) inside it changes based on how it is called. To retain the correct context (i.e. RestaurentInfoController instance as this) inside your callbacks, use arrow functions:
angular.forEach(this.foodTruckList, (item) => {
// ...
console.log(this.restaurentId); // `this` will point to current `RestaurentInfoController` instance here
});

Resources