# Customize StorageDriver
This section will introduce how to customize a StorageDriver, along with related API information and example code.
# Overview
StorageDriver is an interface used for storing and managing data. It is concerned with how data should be stored, deleted, and partitioned. By customizing a StorageDriver, you can implement operations such as reading, writing, and deleting data, and also listen for data changes.
# API Reference
# StorageContext
Class
class StorageContext {
public id: string;
public feature: string;
}
The StorageContext
class represents the storage context used to identify stored data and features.
# PDFViewerStorageContext
Class
class PDFViewerStorageContext extends StorageContext {
public pdfViewer: PDFViewer;
}
The PDFViewerStorageContext
class extends the StorageContext
class and represents the storage context of a PDFViewer
. It contains an instance of PDFViewer
, which allows us to access information such as the current document and current page. By appropriately combining this information, data can be stored in partitioned manner.
# PDFViewer
Interface
interface PDFViewer {
getInstanceId(): string;
}
Starting from version 9.1.0, the PDFViewer
interface has added a method called getInstanceId()
, which is used to retrieve the instance ID of a PDFViewer
. When there are multiple instances of PDFViewer
, it is important to ensure that their IDs are unique in order to partition the data storage for different PDFViewer
instances.
# PDFViewerConstructor
Interface
interface PDFViewerConstructor {
new(options: {
instanceId: string,
customs: {
storageDriver: StorageDriver;
}
}): PDFViewer;
}
The PDFViewerConstructor
interface describes the constructor information for creating a PDFViewer
instance. Starting from version 9.1.0, this constructor adds an options
object containing the properties instanceId
and customs
as parameters. The customs
property includes a StorageDriver
instance used for storing and managing data. By passing a custom StorageDriver
instance via custom.storageDriver
to PDFViewer
, you can achieve custom storage functionality.
# StorageDriver
Interface
interface StorageDriver {
public getAll(context: StorageContext): Promise<Record<string, any>>;
public get<T>(context: StorageContext, key: string): Promise<T | null>;
public set<T>(context: StorageContext, key: string, value: T): Promise<void>;
public removeAll(context: StorageContext): Promise<void>;
public remove(context: StorageContext, key: string): Promise<void>;
public onChange<T>(callback: (event: StorageDriverChangeEvent<T>) => void): Function;
public onRemove(callback: (event: StorageDriverRemoveEvent) => void): Function;
}
The StorageDriver
is an interface that defines a set of methods for storing and managing data. You can customize StorageDriver
by implementing these methods.
getAll(context: StorageContext): Promise<Record<string, any>>
: Retrieves all data in the specified context.get<T>(context: StorageContext, key: string): Promise<T | null>
: Retrieves the data with the specified key in the specified context.set<T>(context: StorageContext, key: string, value: T): Promise<void>
: Sets the data with the specified key in the specified context.removeAll(context: StorageContext): Promise<void>
: Removes all data in the specified context.remove(context: StorageContext, key: string): Promise<void>
: Removes the data with the specified key in the specified context.onChange<T>(callback: (event: StorageDriverChangeEvent<T>) => void): Function
: Registers a callback function to listen for data change events.onRemove(callback: (event: StorageDriverRemoveEvent) => void): Function
: Registers a callback function to listen for data removal events.
# StorageDriverChangeEvent
Interface
interface StorageDriverChangeEvent<T> {
public context: StorageContext;
public key: string;
public oldValue: T;
public newValue: T;
}
The StorageDriverChangeEvent
interface represents an event object for data changes. It contains information such as the context, key, old value, and new value. After a data change occurs, callback function can receive this object.
# StorageDriverRemoveEvent
Interface
interface StorageDriverRemoveEvent {
public context: StorageContext;
public key: string;
}
The StorageDriverRemoveEvent
class represents an event for data removal. It contains information such as the context and key. After a data removal event occurs, callback function can receive this object.
# Customize StorageDriver
To customize a StorageDriver
, you need to create a subclass that implements the StorageDriver
interface and implement its methods.
Following is an example of a custom StorageDriver
based on sessionStorage
(opens new window) implementation:
class MyStorageDriver extends StorageDriver {
// Use a third-party library, https://www.npmjs.com/package/eventemitter3, to implement event distribution
private readonly eventEmitter = new EventEmitter();
// Generate a storage space name based on the context information, primarily for partitioning data
private getSpace(context: StorageContext): string {
const instanceId = context.id;
return [instanceId, context.feature].join('.');
}
async getAll(context: StorageContext): Promise<Record<string, any>> {
const space = this.getSpace(context);
const keys = this.getSpaceKeys(context);
return keys.reduce((result, completeKey) => {
const key = completeKey.slice(space.length + 1);
const rawData = sessionStorage.getItem(completeKey);
result[key] = rawData ? JSON.parse(rawData) : null;
return result;
}, {});
}
async get<T>(context: StorageContext, key: string): Promise<T | null> {
const storageKey = this.generateUniqueKey(context, key);
const valueStr = sessionStorage.getItem(storageKey);
if (valueStr) {
return JSON.parse(valueStr) as T;
} else {
return null as T;
}
}
async set<T>(context: StorageContext, key: string, value: T): Promise<void> {
const storageKey = this.generateUniqueKey(context, key);
const oldValueJSON = sessionStorage.getItem(storageKey);
const oldValue = oldValueJSON ? JSON.parse(oldValueJSON) : undefined;
const newValue = JSON.stringify(value);
if (oldValueJSON === newValue) {
return;
}
sessionStorage.setItem(storageKey, newValue);
this.emitChangeEvent({
context,
key,
oldValue,
newValue: value
});
}
async removeAll(context: StorageContext): Promise<void> {
const keys = this.getSpaceKeys(context);
keys.forEach(key => {
const oldValue = sessionStorage.getItem(key);
sessionStorage.removeItem(key);
const newValue = sessionStorage.getItem(key);
if (newValue !== oldValue) {
this.emitRemoveEvent({
context,
key
} as StorageDriverRemoveEvent);
}
});
}
async remove(context: StorageContext, key: string): Promise<void> {
const storageKey = this.generateUniqueKey(context, key);
const oldValue = sessionStorage.getItem(storageKey);
sessionStorage.removeItem(storageKey);
const newValue =
sessionStorage.getItem(storageKey);
if (newValue !== oldValue) {
this.emitRemoveEvent({
context,
key
});
}
}
onChange<T>(callback: (event: StorageDriverChangeEvent<T>) => void): () => void {
return this.addEventListener<T>('change', callback);
}
onRemove(callback: (event: StorageDriverRemoveEvent) => void): () => void {
return this.addEventListener('remove', callback);
}
private addEventListener<T>(event: 'change' | 'remove', callback: (event: any) => void) {
const listener = e => {
callback(e);
};
this.eventEmitter.addListener(event, listener);
return () => {
this.eventEmitter.removeListener(event, listener);
};
}
// Generate a unique index based on the namespace and key
private generateUniqueKey(context: StorageContext, key: string) {
const space = this.getSpace(context);
return [space, key].join('.');
}
// Get all key-value pairs stored under the namespace
private getSpaceKeys(context: StorageContext): string[] {
const space = this.getSpace(context);
const prefix = space + '.';
return Array(sessionStorage.length)
.fill(0)
.map((_, index) => sessionStorage.key(index))
.filter(it => !!it)
.filter(it => it!.indexOf(prefix) === 0) as string[];
}
// Trigger a data change event when data changes
private emitChangeEvent<T>(event: StorageDriverChangeEvent<T>) {
this.eventEmitter.emit('change', event);
}
// Trigger a data removal event when data is deleted
private emitRemoveEvent<T>(event: StorageDriverRemoveEvent) {
this.eventEmitter.emit('remove', event);
}
}
You can customize StorageDriver
according to your specific needs. During the implementation process, you can use techniques such as asynchronous operations and database queries to achieve data storage and management.
# Use Custom StorageDriver
To use a custom StorageDriver
, you need to create a PDFViewer
instance and pass the custom StorageDriver
as a parameter to the constructor.
Following is an example of using a custom StorageDriver
:
const storageDriver = new MyStorageDriver();
const pdfViewer = new PDFViewer({
instanceId: 'pdf-viewer-1',
customs: {
storageDriver: storageDriver
}
});
When creating a PDFViewer
instance, pass the custom StorageDriver
to the customs
property. PDFViewer will use this StorageDriver
instance to store and manage data.