I am using Angular2. I am getting PDF response as BLOB from backend API. The PDF is showing fine in iframe but it is showing title as 'anonymous'. Can someone please guide?
html code:
<iframe id="showPDFIframe" allowtransparency="false" title="TestPDF" width="100%" height="800" [attr.src]="dataLocalUrl" type="application/pdf"></iframe>
pdf.component.ts
pdfDownload: any;
protected dataLocalUrl: SafeResourceUrl;
ngOnInit() {
this.requestOptions = this.createRequestOptions();
this.requestOptions.responseType = ResponseContentType.Blob;
this._pdfModelService.showPDF(this.requestOptions)
.subscribe( (res) => {
this.pdfDownload = res;
this.dataLocalUrl = this.domSanitizer.bypassSecurityTrustResourceUrl(window.URL.createObjectURL(res));
}, err => {
console.log(err);
})
}
pdfModelService.ts
showPDF(options?: RequestOptions): any {
return this._http.get(this.endpoints.showPDF.uri, options)
.map( (res) => {
return new Blob([res], { type: 'application/pdf' })
});
}
See below image 'Anonymous' is showing
Note: backend API gives the bytes which we cast in BLOB.
have you tried providing title in the options:
showPDF(options?: RequestOptions): any {
return this._http.get(this.endpoints.showPDF.uri, options)
.map( (res) => {
return new Blob([res], { type: 'application/pdf', title: 'testpdf' })
});
}
Although I am not certain why the specified title field "TestPDF" in the code is not appearing on the page, the "(anonymous)" value that is displaying could simply be pulling the meta data from the PDF file itself. A possible solution would be to check the title field in the PDF document properties to set the title there. In Adobe Acrobat, from the file menu select Properties > Description to check/update the title field.
Reference article from W3.org: https://www.w3.org/TR/WCAG20-TECHS/PDF18.html
Related
I am trying to run a WebApp which allows files sharing.
After few google search, I found Web Share API like the standard to do so.
According to the documentation it should works like this using plain JS
This is the code for html page
<p><button>Share MDN!</button></p>
<p class="result"></p>
The code to share all sort "textbased" metadata:
let shareData = {
title: 'MDN',
text: 'Learn web development on MDN!',
url: 'https://developer.mozilla.org',
}
const resultPara = document.querySelector('.result');
if (!navigator.canShare) {
resultPara.textContent = 'navigator.canShare() not supported.';
}
else if (navigator.canShare(shareData)) {
resultPara.textContent = 'navigator.canShare() supported. We can use navigator.share() to send the data.';
} else {
resultPara.textContent = 'Specified data cannot be shared.';
}
The code above works fine, the trouble happens when I try to share files.
According to the documentation it should works like this:
// filesArray is an array of files we want to share (audios, images, videos, pdf)
if (navigator.canShare && navigator.canShare({ files: filesArray })) {
navigator.share({
files: filesArray,
title: 'Pictures',
text: 'Our Pictures.',
})
.then(() => console.log('Share was successful.'))
.catch((error) => console.log('Sharing failed', error));
} else {
console.log(`Your system doesn't support sharing files.`);
}
I started my code from this example and I never success to share a file.
My actual code using React and Typescript looks like this:
//some react code here
const shareNow = async () => {
let imageResponse = await window.fetch('https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png', {mode: "no-cors"});
let imageBuffer = await imageResponse.arrayBuffer();
let fileArray = [new File([imageBuffer], "File Name", {
type: "image/png",
lastModified: Date.now()
})];
if (navigator.canShare && navigator.canShare({ files: filesArray })) {
navigator.share({
files: filesArray
}).then(() => {
console.log('Thanks for sharing!');
})
.catch(console.error);
}
}
//some react code here too
At this point, my typescript compiler yell at me.
Apparently, the navigator object has no method canShare()
I am new to typescript, but I don't understand how and why the navigator could have less attribute since TypeScript is JavaScript superset.
Anyone has an idea on how to solve that except running normal JS ?
Thank you for your time reading this, and I hope to thank you for your answers.
P.S: I also tried a react-component based solution, but all the component I found in open source which wraps Web Share API does not allow file sharing.
Edit
Hey, #DenverCoder9
There is the same use case but using vanilla JS, could anyone try it and tell me what I am doing wrong please ?
<html>
<head>
<title>Sharing Image</title>
<meta charset="UTF-8" />
</head>
<body>
<div className="App">
<img src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"/>
<button id="button">Share</button>
</div>
</body>
<script>
async function shareImage(title, imageUrl) {
const image = await fetch(imageUrl, {mode: "no-cors"});
const blob = await image.blob();
const file = new File([blob], title, { type: 'image/png' });
const filesArray = [file];
const shareData = {
files : filesArray
}
// add it to the shareData
const navigator = window.navigator
const canShare = navigator.canShare && navigator.canShare(shareData) //navigator.canShare()navigator.share //navigator.canShare()
if(canShare){
navigator.share(shareData)
.then(() => console.log('Successful share'))
.catch((error) => console.log('Error sharing', error));
}
else {
console.log("cannot share this file in this context")
}
}
document.getElementById('button').onclick = function() {
shareImage("Title", "https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png")
};
</script>
</html>
I am running this on safari for mac
This is more of a TypeScript issue than a coding issue. Support for the Web Share API (Level 2) was added in this PR, so you can either update to a version of TypeScript that includes this, or alternatively teach your current TypeScript version the relevant types as follows:
type ShareData = {
title? : string;
text? : string;
url? : string;
files?: ReadonlyArray<File>;
};
interface Navigator
{
share? : (data? : ShareData) => Promise<void>;
canShare?: (data?: ShareData) => boolean;
}
I have this code where it gets a random post from r/memes and it get's the post's title with an image attached to it(if there is one), I got this with a lot of help and I just want it to be able to read the post's description. I want to be able to read the text if there is some in the post if I want to use this on like news subreddits or something.
if (msg.content == '-meme') {
function loadMemes() {
// Fetch JSON
return (
fetch('https://www.reddit.com/r/memes.json?limit=800&?sort=hot&t=all')
.then((res) => res.json())
// Return the actual posts
.then((json) => json.data.children)
);
}
function postRandomMeme(message) {
return loadMemes().then((posts) => {
// Get a random post's title and URL
const { title, url } = posts[Math.floor(Math.random() * posts.length)].data;
// Create the embed
const embed = new Discord.RichEmbed({
title,
image: { url },
footer: { text: 'Subreddit : r/memes' },
});
// Send the embed
return message.channel.send(embed);
});
}
// Usage:
postRandomMeme(msg);
// Log all errors
//.catch(console.error);
}
There is a github repository called reddit-bot which is open source and online. you can possibly get an idea from the code provided on there.
https://github.com/lowJ/reddit-bot/blob/master/index.js
I also found this npm package specifically made to get memes and also has an open source github repository.
https://www.npmjs.com/package/#blad3mak3r/reddit-memes
I am creating a form webpart with react but I am stuck at uploading a file, So I want to upload a file when it hit submit button and that has to create sharepoint list item with attachment file.
above image is for reference to understand.
Now I am able to create Above two Subjects and comments in sharepoint list but unsure for upload to attach with same list item as attachments.
<div className={styles.row}>
<ReactFileReader fileTypes={[".csv", ".xlsx", ".Docx", ".pdf"]} base64={true} handleFiles={this.handleFiles.bind(this)}>
<button className='btn' value={this.state.UploadedFilesArray.toString()} >Upload</button>
</ReactFileReader>
</div>
<div className={styles.row}>
<div >
<button id="btn_add" className={styles.button} onClick={this.createItem.bind(this)}>Submit</button>
</div>
The above code is for Upload and Submit, As I said I want to attach the attachment when I submit the form.
private createItem(): void {
this.setState({
status: 'Creating item...',
items: []
});
const body: string = JSON.stringify({
'Title': this.state.subject,
'Comments': this.state.comments,
});
this.props.spHttpClient.post(`${this.props.siteUrl}/_api/Web/lists/getbytitle('${this.props.listName}')/items`,
SPHttpClient.configurations.v1,
{
headers: {
'Accept': 'application/json;odata=nometadata',
//"Accept": "application/json; odata=verbose",
'Content-type': 'application/json;odata=nometadata',
'odata-version': ''
},
body: body
})
.then((response: SPHttpClientResponse): Promise<IListItem> => {
return response.json();
console.log(response)
})
.then((item: IListItem): void => {
this.setState({
status: `Item '${item.Title}' (ID: ${item.Id}) successfully created`,
items: []
});
}, (error: any): void => {
this.setState({
status: 'Error while creating the item: ' + error,
items: []
});
});
}
The above code is for handling the submit, Now can anyone help me creating the handle file function with the functionality that I want to create a sharepoint list item with an attachment when I hit the Submit button. Also with Success or Error message.
I suggest you use this awesome library PNPJS library, it will be easy to work with attachments.
Use (PnPJS) library with SharePoint Framework web parts
Add attachments after item created
private handleFiles(f) {
var filelist = f.fileList;
var fileInfos: IAttachmentFileInfo[] = [];
fileInfos.push({
name: "My file name 1",
content: "string, blob, or array"
});
// loop through files
for (var i = 0; i < filelist.length; i++) {
// get item
let file: File = filelist.item(i);
fileInfos.push({
name: file.name,
content: file
});
}
this.setState({
uploadfiles: fileInfos
});
}
private createItem(): void {
sp.web.lists.getByTitle("mylist").items.add({
'Title': this.state.subject
}).then((r: IItemAddResult) => {
r.item.attachmentFiles.addMultiple(this.state.uploadfiles);
}).then(e => {
console.log("successfully created");
}).catch(e => {
console.log("Error while creating the item" + e)
});
}
For more details, please refer to below demo:
SharedSPFx
Consider this:
An API loads a manifest of image metadata. The images have an ID, and with another API call returns a base64 image from the DB. The model for the manifest is attachmentRecord and the ID is simply a field.
I would rather not preload these large strings into an array (that would work).
so I have this (which lazy loads on any manifest change):
<div v-for="(attachment, index) in attachmentRecord" :key="index">
<img :src="fetchImage(attachment.id)" />
</div>
fetchimage() is a wrapper for an axios function which returns back from a promise. (writing this from memory):
this.axios({
method: "get",
url: url,
}).then(res => res.data)
.catch(() => {
alert("Unable to load raw attachment from this task and ID");
});
}
Now, the network calls go thru fine, the ID passes in correctly, I can see the base 64data, but they don't seem to make it to wrapper function or the src attribute. It always comes up blank. I tried wrapping it in another promise,only to get a promise back to the src attribute. What would be a best practice for this situation in Vue?
Ok, so far I made these changes with Constantin's help:
I tried to strip it down without a helper function:
Vue template Code:
<div v-for="(attachment, index) in attachmentRecord" :key="index">
<img :src="getAttachmentFromTask(attachment.id)" />
base method:
async getAttachmentFromTask(attachmentID) {
if (!attachmentID) alert("Unknown Attachment!");
let sendBack = "";
let url = "/server/..."
await this.axios({
method: "get",
url: url
})
.then(res => {
sendBack = res.data;
})
.catch(() => {
alert("Unable to load raw attachment from this task and ID");
});
// >>>>>>>>>alerts base64 correctly; Vue loads [object Promise] in img
alert(sendBack);
return sendBack;
}
It turns out that Vue doesn't handle async / await as well as I thought. Therefore, you have to save the image data to each attachment in attachmentRecord. This getAttachmentFromTask method now handles this when accessed the first time and populates a data property for the corresponding attachment object. On successive calls, that property is returned if it is already populated. Note the usage of Vue.set() because the property is not available in the initial data, but we want it to be reactive. You can even set up a fallback image like a loader, see the shortly flickering SO logo without text before the larger logo appears:
new Vue({
el: '#app',
data: {
attachmentRecord: [{
id: 1
}]
},
methods: {
getAttachmentFromTask(attachmentIndex, attachmentID) {
let record = this.attachmentRecord[attachmentIndex];
if (!record.data) {
Vue.set(record, 'data', null);
axios.get('https://kunden.48design.de/stackoverflow/image-base64-api-mockup.json').then((result) => {
Vue.set(record, 'data', result.data);
});
}
return this.attachmentRecord[attachmentIndex].data;
}
}
});
img {
max-width: 100vw;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.min.js"></script>
<div id="app">
<div v-for="(attachment, index) in attachmentRecord" :key="index">
<img :src="getAttachmentFromTask(index, attachment.id) || 'https://cdn.sstatic.net/Sites/stackoverflow/img/apple-touch-icon.png'" />
</div>
</div>
old answer: (Unfortunately doesn't work that way with Vue currently)
Axios requests are asynchronous by default. So the function doesn't wait for then() to return the value. You could add the async keyword before your fetchImage function name and add the await keyword before this.axios. Then make the then callback assign the return value to a variable in the fetchImage function scope and have the function return it.
async fetchImage() {
let returnValue;
await this.axios({
method: "get",
url: url,
}).then(res => { returnValue = res.data; })
.catch(() => {
alert("Unable to load raw attachment from this task and ID");
});
return returnValue;
}
Can you tell me why this code is not working?
Note: file is native plugin
var blob = new Blob(["This is my blob content"], { type: "text/plain" });
this.file.writeFile(this.file.dataDirectory, 'myletter.txt', blob, { replace: true })
.then(() => {
//code
})
.catch((err) => {
console.error(err); //it comes to here
});
It gives this exception:
FileError
code : 5
message : "ENCODING_ERR"
__proto__ : Object
I have found the issue here. That was due to this path this.file.dataDirectory.
Solution: Use this instead this.file.externalApplicationStorageDirectory