Import multiple Annotations in Pdftron with NodeJs - node.js

In my application annotations are stored in the database as single annotations. For one document in the document table I store many annotations (multiple xfdf strings) in the annotation table.
I wrote a code to generate the pdf and import these annotations. I referred following links for this code,
https://www.pdftron.com/documentation/web/guides/features/forms/import-data/
https://groups.google.com/g/pdfnet-sdk/c/gXaG5X-zpR8
params,
annotations : list of annotations with xfdf String
downloadedFile : pdf file as a buffer
isFlatternAnnotations - is a boolean option to
flatten annotations
async importAnnotationsToDocument(
annotations: any,
downloadedFile: any,
isFlatternAnnotations: any,
) {
await PDFNet.initialize();
const pdfDocument = await PDFNet.PDFDoc.createFromBuffer(downloadedFile);
pdfDocument.lock();
let fdfDocument = null;
annotations.forEach(async annotation => {
fdfDocument = await PDFNet.FDFDoc.createFromXFDF(annotation.xfdfString);
await pdfDocument.fdfMerge(fdfDocument);
});
if (isFlatternAnnotations === 'true') {
await pdfDocument.flattenAnnotations();
}
const documentBuffer = await pdfDocument.saveMemoryBuffer(
PDFNet.SDFDoc.SaveOptions.e_remove_unused,
);
const documentBufferResponse = Buffer.from(documentBuffer);
PDFNet.shutdown();
return documentBufferResponse;
}
However I noticed the code is working only the await pdfDocument.flattenAnnotations(); is running. If it is not running annotations are not merged in the document.
And also if it runs a single time, the annotations are displayed without flattening. But if I add the same line three times it works correctly.
I think the way I have done this is not correct. I need your help to write this code correctly.
Following code works correctly, but there should be a proper way to do this.
async importAnnotationsToDocument(
annotations: any,
downloadedFile: any,
isFlatternAnnotations: any,
) {
await PDFNet.initialize();
const pdfDocument = await PDFNet.PDFDoc.createFromBuffer(downloadedFile);
pdfDocument.lock();
let fdfDocument = null;
annotations.forEach(async annotation => {
fdfDocument = await PDFNet.FDFDoc.createFromXFDF(annotation.xfdfString);
await pdfDocument.fdfMerge(fdfDocument);
});
if (isFlatternAnnotations === 'true') {
await pdfDocument.flattenAnnotations();
await pdfDocument.flattenAnnotations();
await pdfDocument.flattenAnnotations();
} else {
// This shows the annotations without flattening
await pdfDocument.flattenAnnotations();
}
const documentBuffer = await pdfDocument.saveMemoryBuffer(
PDFNet.SDFDoc.SaveOptions.e_remove_unused,
);
const documentBufferResponse = Buffer.from(documentBuffer);
PDFNet.shutdown();
return documentBufferResponse;
}
Following is the xfdf String for a single annotation
<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/" xml:space="preserve">
<fields />
<add>
<square page="0"
rect="387.88,525.73,525,609.07"
color="#FFCD45" flags="print"
name="d1aa1a2a-822f-507b-6ff6-d61bcc6bd862"
title="test.title" subject="Rectangle"
date="D:20210405104448+08'00'"
interior-color="#FFCD45"
opacity="0.5"
creationdate="D:20210405104445+08'00'" />
</add>
<modify /><delete />
</xfdf>

You are loading a variant of XFDF, called Command, which you can see documented here. https://www.pdftron.com/documentation/web/guides/xfdf/
The following code is how you would load and apply XFDF command XML data to a PDFDoc instance.
let fdfDoc = await pdfDoc.fdfExtract(PDFNet.PDFDoc.ExtractFlag.e_both);
await fdfDoc.saveAsXFDF('data.xfdf');
fdfDoc = await PDFNet.FDFDoc.createFromXFDF('data.xfdf');
await fdfDoc.mergeAnnots(str);
await pdfDoc.fdfUpdate(fdfDoc);
await pdfDoc.refreshAnnotAppearances();

The API pdfDocument.flattenAnnotations() by default only flattens FormFields, and not annotations.
https://www.pdftron.com/api/pdfnet-node/PDFNet.PDFDoc.html#flattenAnnotations__anchor
So I would recommend changing the call to
pdfDocument.flattenAnnotations(false);

Related

Firebase does not recognize document path

So I have the following request with which I am trying to do the following:
Look for a collection with the name 'questionnaires'
Look at a specific document with id, given by the parameter of the function
Take the reference of a collection 'questions' within this document
public async getQuestionnaire(
questionnaireId: string,
): Promise<FirebaseFirestore.CollectionReference<DocumentData>> {
const questionsCollection = await getFirestore().collection(
'questionnaires/{questionnaireId}/questions/',
);
return questionsCollection;
}
I tried both by obtaining the reference in one line or seperating it the following way:
public async getQuestionnaire(
questionnaireId: string,
): Promise<FirebaseFirestore.CollectionReference<DocumentData>> {
const questionnairesCollection =
getFirestore().collection('questionnaires');
const desiredQuestionnaire = questionnairesCollection.doc(questionnaireId);
const questions = desiredQuestionnaire.collection('questions');
return questions;
}
The method is called from the following get request handler:
#Get('/firebase/questionnaire/:questionnaireId')
#OpenAPI({ summary: 'Return a questionnaire' })
async getQuestionnaire(#Param('questionnaireId') questionnaireId: string) {
const questionsRef = await this.firebaseService.getQuestionnaire(
questionnaireId,
);
return { data: questionsRef };
}
The interesting thing is that if I print out the reference, I get exactly what I want. However when I try to return it, I get the following error:
[GET] /firebase/questionnaire/3iz5K3hsVvaFBwkyzd57 >> StatusCode:: 500,
Message:: Value for argument "documentPath" is not a valid resource path. Path must be a non-empty string.

How can I store an API call response as an object or parse an array as a JSON?

My app is trying to make an API call and display the data from it in a modal. It isn't working because I'm storing the response from the API call as an array at the moment, meaning I can't access the sub-elements of the response.
My question, then: is there some way to either:
Store an object using State so that I can conveniently reference parts of that object?
OR
Parse an array as a JSON so I can take the data from the response and process it as a JSON when needed?
I realise this is kind of a weird question, so any answer that would achieve the same result would also be great.
Here's my code:
const DrinkPopup = (props) => {
let [drinkDetails,setDrinkDetails] = useState([])
let selectedDrinkId = props.drink
const getDrinkAsync = async (selectedDrinkId) => {
try {
let response = await fetch('https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i='+selectedDrinkId);
let data = await response.json();
setDrinkDetails(data.drinks)
return true;
} catch (error) {
console.error(error);
}
}
useEffect (() => {
getDrinkAsync(selectedDrinkId)
console.log(selectedDrinkId)
console.log(drinkDetails)
},[]);
return(
<Modal isVisible={props.modalVisible}
onBackdropPress={()=>{props.setModalVisible(false)}} //allows closing modal by tapping outside it or back button
onBackButtonPress={()=>{props.setModalVisible(false)}}
animationIn={"slideInUp"}>
<View style={styles.infocard}>
<View style={styles.titleBox}>
<Text style={styles.header}>{drinkDetails.idDrink}</Text>
</View>
</View>
</Modal>
)
}
A really easy way to convert your array to an object is to use Object.assign like so:
Object.assign({}, data.drinks)
It would return an object with numbered keys and you can store that to state.

Syncing React-Slick with Query Params

I'm using React-Slick to render <Report /> components in a carousel. I would like to sync each <Report />'s reportId with query params.
For example, a user would be able to see a specific report by going to myapp.com/reports?id=1 and it would take them to that specific "slide".
The problem I'm having is that the report data is being loaded before the slides are initialized. I can't find any good examples of react-slick's onInit or onReInit.
Instead of using onInit or onReInit, I just utilized the initialSlide setting and used componentDidUpdate().
componentDidUpdate = (prevProps, prevState) => {
const queryParams = qs.parse(this.props.location.search)
if (prevProps.reports !== this.props.reports) {
const sortedReports = this.sortReportsByDate(this.props.reports)
this.setSlideIndex(sortedReports.findIndex(i => i.id === parseInt(queryParams.reportId, 10)))
this.setQueryParams({
reportId: this.sortReportsByDate(this.props.reports)[this.state.slideIndex].id
})
}
if (prevState.slideIndex !== this.state.slideIndex) {
this.setQueryParams({
reportId: this.sortReportsByDate(this.props.reports)[this.state.slideIndex].id
})
}
}
And the settings:
const settings = {
...
initialSlide: reportsSortedByDate.findIndex(i => i.id === parseInt(queryParams.reportId, 10))
...
}
I hope someone finds this useful!

How to delete items attachement from sharepoint List using pnpjs?

Hi Im trying to delete attached item from list items first and then upload new attach file in sharepoint using pnp js (in vuejs)!
i trace the code and delete part is runnging but i dont konw why attached file doesnt delete!!!
this is my code for deleting attached item
public async DeleteItemsAttachment(itemId: number): Promise<any> {
let item = pnp.sp.web.lists.getById('{128EF67A-FDSF-4F42-8E8F-D3FC9523273E}').items.getById(itemId)
return await item.attachmentFiles.deleteMultiple()
}
public async AddanAttachment(itemId: number, fileName: string, arrayBuffer: File): Promise<any> {
let item = pnp.sp.web.lists.getById('{128EF6AA-FD8F-4F42-8E8F-D3FC9523273E}').items.getById(itemId)
return await item.attachmentFiles.add(fileName, arrayBuffer)
}
uploadFile(){
if (this.itemId != null && this.myfiles) {
if (this.HasUploadFile) {
this.spService.DeleteItemsAttachment(this.itemId).then(response => {
this.AddAnAttachmentToRecord()
}).catch(e => {
this.message = ` exception : ${e}`
})
}
else {
this.AddAnAttachmentToRecord()
}
}
How can i solve my problem?
where is wrong area of my code?
According to attachmentfiles.ts deleteMultiple function expects the array of attachment file names, so attachments could be deleted by explicitly providing file names:
item.attachmentFiles.deleteMultiple("{attachment-file-name-1}","{attachment-file-name-2}")
or (more dynamic way) by reading attachment names and specifying it in deleteMultiple function:
let item = sp.web.lists.getByTitle(listTitle).items.getById(itemId);
//1. get all attachments
let attachments = await item.attachmentFiles.get();
let attachmentNames = attachments.map(a => a.FileName);
//2. delete all attachmanents
await item.attachmentFiles.deleteMultiple(...attachmentNames);

Passing path parameters in axios

I am using Axios with NodeJs and trying to pass path parameters in axios.get() method. For example, if URL is url = '/fetch/{date}', I want to replace {date} with the actual date while calling axios.get(url).
I went through the source code on Github and StackOverflow, but couldn't find any method.
Is it possible to keep URLs with parameters as a placeholder and replace them while actually calling the get method of Axios?
Axios doesn't have this feature and it looks like the team don't want to add it.
With credit to previous responders for inspiration, to me this seems like the solution closest to what you (and me) are looking for:
1 - Where you want to store all your URLs and their parameters, define them as functions which use a template string to return the composed URL:
export var fetchDateUrl = (date) => `/fetch/${date}`;
If you need any type-specific formatting of the value being concatenated into the URL, this function is a good place to do it.
2 - Where you want to make the request, call the function with the correct parameters:
import { fetchDateUrl } from 'my-urls';
axios.get(fetchDateUrl(someDateVariable))...;
Another variation, if you really like the idea of naming the parameters at the call site, you can define the URL function to destructure an object like this:
var fetchDateUrl = ({date}) => `/fetch/${date}`;
which you'd then use like this:
axios.get(fetchDateUrl({date: someDateVariable}));
Use template strings
url = `/fetch/${date}`
Or just tag it on
url = '/fetch/'+ date
I think using axios interceptors is better to do this :
//create your instance
const instanceAxios = axios.create({
baseUrl: 'http://localhost:3001'
]);
instanceAxios.interceptors.request.use(config => {
if (!config.url) {
return config;
}
const currentUrl = new URL(config.url, config.baseURL);
// parse pathName to implement variables
Object.entries(config.urlParams || {}).forEach(([
k,
v,
]) => {
currentUrl.pathname = currentUrl.pathname.replace(`:${k}`, encodeURIComponent(v));
});
const authPart = currentUrl.username && currentUrl.password ? `${currentUrl.username}:${currentUrl.password}` : '';
return {
...config,
baseURL: `${currentUrl.protocol}//${authPart}${currentUrl.host}`,
url: currentUrl.pathname,
};
});
// use like :
instanceAxios.get('/issues/:uuid', {
urlParams : {
uuid: '123456789'
}
})
For typescript users, you will need to add this, in one of your .d.ts
declare module 'axios' {
interface AxiosRequestConfig {
urlParams?: Record<string, string>;
}
}
( this is a POC, not really tested, doesn't hesitate if you see something wrong )
You can use template strings ie:
let sellerId = 317737
function getSellerAnalyticsTotals() {
return axios.get(`http://localhost:8000/api/v1/seller/${sellerId}/analytics`);
}
Given some API /fetch/${date} you likely want to wrap your axios call in a function.
const fetchData = (date) => axios.get(`/fetch/${date}`);
fetchData(dateObject.toFormat('yyyy-mm-dd'))
.then(result => { ... });
This requires the calling code to format date correctly however. You can avoid this by using a DateTime library that handles date string parsing and do the format enforcement in the function.
const fetchData = (date) => axios.get(`/fetch/${date.toFormat('yyyy-mm-dd')}`);
fetchData(dateObject)
.then(result => { ... });
you can do like this:
getProduct = (id) => axios.get(`product/${id}`);
I always do it like this:
const res = await axios.get('https://localhost:3000/get', { params: { myParam: 123 } });
I find this to be much clearer than template strings.
More explanation here

Resources