SPFx uploading and adding attachment to a list - json

I am having some difficulty upload and attachment to a list item in sharepoint using the PNP/SP package. I dont have much experience with the input file component so I think I may be missing a step between the file upload html element and submitting the file to the SharePoint web service.
So far I've tried to follewing the PNP example with a few changes https://pnp.github.io/pnpjs/sp/docs/attachments/ and tried a few different arguments but they all tend to result in 409 or 500 errors, one error mentions that it's attempting a GET request instead of post.
My code is below and i'll post full error messages when i get into the office tomorrow but any help would be greatly appreciated.
private setButtonsEventHandlers(): void {
let fileUpload = document.getElementById("fileUploadInput")
if(fileUpload) {
fileUpload.addEventListener('change', () => {
this.uploadFiles(fileUpload);
});
}
}
private async uploadFiles(fileUpload) {
let file = fileUpload.files[0];
let attachmentsArray = this.state.attachmentsToUpload;
let _web = new Web(this.props.wpContext.pageContext.site.absoluteUrl);
let _listItem;
let listUrlSplit: string[] = this.props.listUrl.split("/");
let listName: string = listUrlSplit[listUrlSplit.length-1];
_listItem = await _web.lists.getByTitle(listName).items.getById(this.props.id);
let attachmentUpload = await _listItem.attachmentFiles.add(file.name,file)
}
I tested the code (below) by replacing my file upload with strings and it does work so I think my error is in misunderstanding the input file element
let attachmentUpload = await _listItem.attachmentFiles.add("Testfile.txt","This is test content")
Thanks in advance all and enjoy whats left of sunday ;)
Andy

Here is my simple test demo which works(React framework).
Component .tsx
<div className={ styles.column }>
<input type='file' id='fileUploadInput' name='myfile'/>
<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>
webpart.ts
public render(): void {
const element: React.ReactElement<IPnpspUploadAttachementProps > = React.createElement(
PnpspUploadAttachement,
{
description: this.properties.description
}
);
ReactDom.render(element, this.domElement);
this.setButtonsEventHandlers();
}
private setButtonsEventHandlers(): void {
let fileUpload = document.getElementById("fileUploadInput")
if(fileUpload) {
fileUpload.addEventListener('change', () => {
this.uploadFiles(fileUpload);
});
}
}
private async uploadFiles(fileUpload) {
let file = fileUpload.files[0];
//let attachmentsArray = this.state.attachmentsToUpload;
let item = sp.web.lists.getByTitle("MyList").items.getById(15);
item.attachmentFiles.add(file.name,file).then(v => {
console.log(v);
});
//let attachmentUpload = await _listItem.attachmentFiles.add(file.name,file)
}

Related

Generate Image from HTML String in React

I'm trying to generate some content for an HTML source dynamically, and then convert it to an Image (JPG or PNG) for later conversion into Base64.
For prefilling the HTML I'm using VelocityJS. And then attempting to use html-to-image library to convert the formatted HTML String/Element to an Image.
But I keep getting the following error.
oops, something went wrong! TypeError: Cannot read properties of undefined (reading 'defaultView')
at px (util.ts:69:1)
at getNodeWidth (util.ts:75:1)
at getImageSize (util.ts:87:1)
at toCanvas (index.ts:32:1)
at Module.toJpeg (index.ts:83:1)
at usePaymentReceiptBuilder.js:26:1
I first tried passing the string itself to the library. Which didn't work either. Then attempted adding the string into a div, and then wrapping it with JQuery to create an element. None of these worked.
The code is as follows.
import velocityjs from 'velocityjs';
import * as htmlToImage from 'html-to-image';
import paymentReceiptHtml from '../resources/email-receipts/payment-receipt.txt';
import useBusinessInfo from "./useBusinessInfo";
import $ from 'jquery';
const usePaymentReceiptBuilder = () => {
const {businessId, businessInfo, currencySymbol} = useBusinessInfo();
const PAPER_SIZE_TWO_INCH = 384;
const printReceiptOnCloverDevice = (purchaseEntry) => {
fetch(paymentReceiptHtml)
.then((response) => response.text())
.then((paymentReceiptHtmlTextContent) => {
let receiptContent = buildReceiptContent(purchaseEntry);
let velocityContext = {
"receiptContent": receiptContent
};
let renderedString = velocityjs.render(paymentReceiptHtmlTextContent, velocityContext);
// let htmlContent = <div dangerouslySetInnerHTML={{__html: renderedString}}/>;
let htmlContent = $(renderedString)
htmlToImage.toJpeg(htmlContent)
.then(function (dataUrl) {
console.log("generateReceiptBase64String 4");
console.log(dataUrl);
})
.catch(function (error) {
console.error('oops, something went wrong!', error);
});
});
}
const buildReceiptContent = (purchaseEntry) => {
return {
businessName: businessInfo.businessName,
businessAddress: businessInfo.address,
businessWebsite: "",
businessContactNumber: businessInfo.contactNumbers.length > 1 ? businessInfo.contactNumbers[0] : "",
businessLogoUrl: "",
receiptOrderContent: purchaseEntry,
instanceMarketingUrl: "",
orderQrCodeBase64: "",
paperWidth: PAPER_SIZE_TWO_INCH
};
}
return {printReceiptOnCloverDevice}
}
export default usePaymentReceiptBuilder
What am I doing wrong? Please assist.

GatsbyImage working when pulling data locally but not with Strapi and Gatsby

I'm on the verge of quitting (again!!) but keeping at it..
Would really appreciate some help on this or my laptop may be thrown out the window soon!
I set up a project locally and am now linking it to content on Strapi. I'm able to add the text data from Strapi fine but what I'm really struggling with is the GatsbyImage data.
I'm getting this error:
Warning: Failed prop type: The prop image is marked as required in GatsbyImage, but its value is undefined.
and here's my code:
import React from "react";
import { SubTitle } from "../components/styles/Styles";
import { GatsbyImage, getImage } from "gatsby-plugin-image";
import { graphql, useStaticQuery } from "gatsby";
import { ImageLayout } from "../components/styles/GridLayout";
// featured products on home page
const query = graphql`
{
allStrapiNewArrivals {
nodes {
images {
localFile {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
layout: CONSTRAINED
height: 400
width: 400
)
}
}
}
}
}
}
`;
const FeatureProducts = () => {
const data = useStaticQuery(query);
const nodes = data.allStrapiNewArrivals.nodes;
console.log(nodes);
return (
<div>
<SubTitle>New Arrivals</SubTitle>
<ImageLayout>
<div>
<div className="collection-cards">
{nodes.map((image, index) => {
const pathToImage = getImage(image);
return (
<GatsbyImage
image={pathToImage}
alt=""
key={index}
className="collection"
/>
);
})}
</div>
</div>
</ImageLayout>
</div>
);
};
export default FeatureProducts;
When I console.log(nodes) it returns:
[{…}]
0:
images: Array(3)
0: {localFile: {…}}
1: {localFile: {…}}
2: {localFile: {…}}
length: 3
__proto__: Array(0)
__proto__: Object
length: 1
__proto__: Array(0)
My thoughts - in the allStrapiNewArrivals data, could the 'images{ localFile ' bit be the cause? because these aren't listed when pulling the data locally. eg. should it read: 'file{childImageSharp'
I've tried using 'const nodes = data.allStrapiNewArrivals.nodes.images.localFile' but this is also throwing an error of:
Cannot read property 'map' of undefined
or could it be the getImage() in the .map function? - const pathToImage = getImage(image);
If anyone can help I'd be so grateful, I've been stuck on this for ages!
images is an array of images so you would have to map over it too. Also try gatsby clean
nodes.map((node, index) => {
return node.images.map(image => {
const pathToImage = getImage(image.localFile);
return ( <
GatsbyImage image = {
pathToImage
}
alt = ""
key = {
index
}
className = "collection" /
>
);
})
})

CKEditor Blazor integration

I am trying to use CKeditor with Blazor.
I used Online builder to create a custom build, with ImageUpload and Base64UploadAdapter, and it is integrated in BlazorApp.
I can successfully show it on the page, and put / get HTML content from it.
Source of the working version for Blazor app is here https://gitlab.com/dn-misc/BlazorCKEditor1/
But as I would like to inser image as Base64 encoded string directly in HTML content, when I try to upload image I get following error:
Assertion Failed: Input argument is not an HTMLInputElement (from content-script.js)
I have successfully implemented Chris Pratt implementation. Check this out:
IMPORTANT: this works with ClassicEditor ONLY.
Blazor component, I called mine InputCKEditor.razor. Yeah I know, no very original.
#namespace SmartApp.Components
#inherits InputTextArea
#inject IJSRuntime JSRuntime
<textarea #attributes="AdditionalAttributes"
id="#Id"
class="#CssClass"
value="#CurrentValue"></textarea>
#code {
string _id;
[Parameter]
public string Id
{
get => _id ?? $"CKEditor_{_uid}";
set => _id = value;
}
readonly string _uid = Guid.NewGuid().ToString().ToLower().Replace("-", "");
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await JSRuntime.InvokeVoidAsync("CKEditorInterop.init", Id, DotNetObjectReference.Create(this));
await base.OnAfterRenderAsync(firstRender);
}
[JSInvokable]
public Task EditorDataChanged(string data)
{
CurrentValue = data;
StateHasChanged();
return Task.CompletedTask;
}
protected override void Dispose(bool disposing)
{
JSRuntime.InvokeVoidAsync("CKEditorInterop.destroy", Id);
base.Dispose(disposing);
}
}
Then, you have to put this in your interop.js
CKEditorInterop = (() => {
var editors = {};
return {
init(id, dotNetReference) {
window.ClassicEditor
.create(document.getElementById(id))
.then(editor => {
editors[id] = editor;
editor.model.document.on('change:data', () => {
var data = editor.getData();
var el = document.createElement('div');
el.innerHTML = data;
if (el.innerText.trim() === '')
data = null;
dotNetReference.invokeMethodAsync('EditorDataChanged', data);
});
})
.catch(error => console.error(error));
},
destroy(id) {
editors[id].destroy()
.then(() => delete editors[id])
.catch(error => console.log(error));
}
};
})();
Now time to use it:
<form>
<label class="col-xl-3 col-lg-3 col-form-label text-sm-left text-lg-right">Description</label>
<div class="col-lg-9 col-xl-6">
<InputCKEditor #bind-Value="_model.Description" class="form-control form-control-solid form-control-lg"></InputCKEditor>
<ValidationMessage For="#(() => _model.Description)" />
</div>
</form>

Angular Firebase Storage, Assigning User Input Properties to Real Time Database

I want to upload a file especially an image to my firebase storage. I found a tutorial from this link. I added the more properties like url and file to my existing class and i followed the function template on that link. But apparently i did something wrong. The file uploaded to my storage and the console log didn't return any error. I need help with assigning another properties like prdName, prdCategory, and prdSup with user input correctly. Can someone help me with this please?
//product.ts
export class Product {
$prdKey: string;
prdName: string;
prdCategory: string; //Category
prdSup: string; //supplier
prdDescription: string;
prdImage: string; //name
prdUrl: string; //url
file: File;
constructor(file: File) {
this.file = file;
}
}
//service.ts
variable: any;
selectedProduct: Product = new Product(this.variable); //-->there was an error here that said expected 1 argument but got 0 so i add variable:any;
private basePath = '/product';
pushFileToStorage(Product: Product, progress: {
percentage: number
}) {
const storageRef = firebase.storage().ref();
const uploadTask = storageRef.child(`${this.basePath}/${Product.file.name}`).put(Product.file);
uploadTask.on(firebase.storage.TaskEvent.STATE_CHANGED,
(snapshot) => {
// in progress
const snap = snapshot as firebase.storage.UploadTaskSnapshot
progress.percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100)
},
(error) => {
// fail
console.log(error)
},
() => {
// success
/*--What should i assign here?--*/
Product.prdName = Product.file.name,
Product.prdCategory = Product.file.name,
Product.prdSup = Product.file.name,
Product.prdDescription = Product.file.name,
/*------------------------------------------*/
Product.prdUrl = uploadTask.snapshot.downloadURL,
Product.prdImage = Product.file.name,
this.saveFileData(Product)
}
);
}
private saveFileData(Product: Product) {
this.firebase.list(`${this.basePath}/`).push(Product);
}
//component.ts
upload() {
const file = this.selectedFiles.item(0);
this.currentFileUpload = new Product(file);
this.ProductService.pushFileToStorage(this.currentFileUpload, this.progress);
}
<!--component.html-->
<!--form snippet-->
<form #productForm="ngForm" (ngSubmit)="upload()">
<div class="form-group">
<label>Product Name</label>
<input class="form-control" name="prdName" #prdName="ngModel" [(ngModel)]="ProductService.selectedProduct.prdName">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
Please let me know if more snippets are needed. Thank you in advance.
(Update)
I put the push function inside //success condition, however i'm not sure what to assign for each class properties. Product.prdName = Product.file.name, will give me prdName equal to the file name. I tried Product.prdName = selectedProduct.prdName, but looks like it is not correct.
I figured it out, it should looks like this, works for me :D
() => {
// success
this.productList.push({
prdName: this.selectedProduct.prdName,
prdCategory: this.selectedProduct.prdCategory,
prdSup: this.selectedProduct.prdSup,
prdDescription: this.selectedProduct.prdDescription,
prdUrl: this.selectedProduct.prdUrl = uploadTask.snapshot.downloadURL,
prdImage: this.selectedProduct.prdImage = Product.file.name,
})
this.saveFileData(Product)
}

How to use *ngFor for each my category

Can anyone help me out for this. I have four label tabs and these tabs will have projects. I want when a project is saved this have to be in the same tab i opened the dialog to save the project not in all my tabs.
Hope you will help me out with these.
<label for="tab-one">PROPOSITIONS
<a class="plus"
(click)="opentestdialog('PROPOSITIONS'); false">+</a>
</label>
<div *ngFor="let project of projects$ | async; let i = index;">
This opens the dialog it is in typescript.
opentestdialog(category) {
this.dialog.open(TestdialogComponent, { data: category });
}
And this is the dialog that saves the project
save() {
if ( this.newProjectName.length > 0) {
this.working = true;
const newProject: Project = emptyProject();
newProject.name = this.newProjectName;
newProject.id = Math.random().toString();
newProject.state = this.newState;
newProject.type = this.newType;
newProject.category = this.category;
this.store.dispatch(new UpsertProjectInternalAction(newProject));
this.newProjectName = '';
}
}
}
Thank's everyone that looked my Question and didn't answer.
I found the solution for this.
getProjectOfCategory(category: string) {
return this.projects.valueSeq().filter(project => category ===
project.category).toArray();
}