RELEASE: What's New in Summer 2024
By Roger Dunham | 2024 Aug 08
4 min
Tags
annotation
javascript
Summary: This article discusses PDF annotations and how they can be managed in Apryse WebViewer. It explains how to control user permissions for viewing, editing, or deleting annotations using the setPermissionCheckCallback function on AnnotationManager, allowing fine-tuned access based on user roles.
We all know that PDFs are a great and portable file format that is widely understood. But what about annotations within them?
Annotations come in many forms, such as text, graphics, highlights, text boxes, and redaction marks, to name just a few. Technically, annotations are separate from the PDF content and can even be exported separately into a database, allowing version control to be implemented, making them even more awesome.
Want an in-depth look at annotations? Check out our Comprehensive Guide to PDF Annotations with JavaScript.
By default, users can only modify the annotations they create, but you can customize this. The permissions include:
In the recent article about Role Based Access Control, we saw how we can use those options to set up a system within Apryse WebViewer to control which annotations can be viewed, edited or deleted by specific users.
In this article, we will explore how to fine-tune user permissions by using the setPermissionCheckCallback function on AnnotationManager.
This function, if present, determines whether a specific annotation is editable. It should define a callback that returns true if the annotation can be edited or false if it cannot.
What’s more, if the function is present, then it overrides the permissions that would be used for an Admin or User (as defined above). It does not, however, override the AnnotationManager read-only mode.
Having access to a callback that works in this way opens the possibility of solving a whole range of use-cases that require sophisticated handling.
For example, you can have:
In fact, if you wanted, then you could use the system to prevent annotations from being modified unless they were created on a Monday by an author called “Jeremy,” which is located on the right-hand side of page two.
I have no idea why you would want to do that, but the flexibility to do so is one reason why the Apryse SDK is a preferred choice for many developers – and by the end of this article, you will see how it can be done.
Since this callback will be used every time that WebViewer needs to know what the permissions should be for an annotation, we need to implement the default behavior. We can then extend the code to include our custom behavior. As a result, typical code will look something like this:
annotationManager.setPermissionCheckCallback((author, annotation) => {
// the default permission check that is used
const defaultPermission = annotation.Author === annotationManager.getCurrentUser() || annotationManager.isUserAdmin();
// Specify a custom check
const customPermission = [add code logic here]
// You can return the customPermission as it is, or else return the least strict permission
return defaultPermission || customPermission;
});
The exact implementation of the code is up to you, of course, and can be tailored to suit your requirements. Let’s look at a few examples.
Some people like squiggly underlines, other people don’t. Let’s create a system where any user can delete squiggly underlines, irrespective of who created them.
Normally, this would not be allowed since the out-of-the-box behavior is that annotations can only be deleted by the person who created them or by an Admin.
In order to implement customer permission, we need to know if the selected annotation, passed to the callback, is of the Core.Annotations.TextSquigglyAnnotation type.
A gotcha here: If you have come from a .NET background (like I did), it is that, as in TypeScript, it is a Type Assertion, not a true cast. This means that annotation, as TextSquigglyAnnotation, will result in an object (assuming annotation is non-null) even if the annotation is not of the specified type.
If you want to know whether the annotation is a particular type, then use an instance instead.
If the annotation is a squiggly, then it should be editable so we can return true. If not, then the default value should be returned (since we also need to handle when the annotation type is, for example, free text).
You can implement this with the following code.
annotationManager.setPermissionCheckCallback((author, annotation) => {
// the default permission check that is used
// you can combine this with your own custom checks
const defaultPermission = annotation.Author === annotationManager.getCurrentUser() || annotationManager.isUserAdmin();
const isSquiggly = annotation instanceof Core.Annotations.TextSquigglyAnnotation
const customPermission = isSquiggly
return defaultPermission || customPermission;
});
Let’s see what that looks like. Let’s open a PDF with squiggly underlines added by another author. By default, we won’t be able to delete one created by Justin.
Figure 1 - By default a user cannot delete an annotation unless they created it.
However, if we use our custom code then we can edit, add links to, or delete the annotation.
Figure 2 - Using setPermissionCheckCallback means that you can permit any user to delete squiggly underlines.
OK, let’s look at another example. We have a PDF, and many users can add comments via sticky notes.
We want to implement a logic so that if the text within a comment begins with ‘Review’ then the annotation is editable by any user. If the text starts differently, then the default rules should apply, with only the author or an administrator being able to edit or delete the annotation.
Just as before we can use instanceof to create custom logic that only relates to sticky notes.
If one is found, then we get the text from the annotation using getContents().
Finally, we check whether the text starts with ‘Review’ (allowing for case differences). If so then the annotation is editable, and if not, then the editability is controlled by the default mechanism.
annotationManager.setPermissionCheckCallback((author, annotation) => {
// the default permission check that is used
// you can combine this with your own custom checks
const defaultPermission = annotation.Author === annotationManager.getCurrentUser() || annotationManager.isUserAdmin();
const isSticky = annotation instanceof Core.Annotations.StickyAnnotation
if (isSticky) {
const contents = annotation.getContents()
const customPermission = (contents?.toLowerCase()?.startsWith("review:"))
return defaultPermission || customPermission;
}
return defaultPermission;
});
In this example I have chosen to return from multiple locations in the code, but if you prefer the coding style of just a single return then the code can easily be modified to do so.
Let’s see what happens when we use that code when logged in as “Guest”, and try to edit a sticky note that was created by Sally.
Figure 3 - Attempting to add an annotation that was created by a different user is not allowed by default.
Since the comment text does not start with ‘Review:’ the annotation is read-only (you can tell that by the red outline).
On the other hand, if the text starts with ‘Review:’ then the custom permissions are used, and the annotation can be modified or deleted (and the outline is blue).
Figure 4 - The text in the comment has been used to identify that the annotation should be given custom permissions, even though it was created by a different user.
While we have used simple examples, the mechanism can be made as sophisticated as you require. There are many other properties that you can use to control the permissions logic. For example:
Put those together and you can create custom permissions even for gnarly business rules, such as the one that I mentioned at the start of the article.
Awesome!
There is a whole world of functionality available, so check out the documentation for annotations to dive in.
If you run into any problems, please reach out to us on Discord and our helpful Solution Engineers will be happy to help.
Tags
annotation
javascript
Roger Dunham
Share this post
PRODUCTS
Enterprise
Small Business
Popular Content