Products

Spreadsheet Images

When a user uploads an image, it’s stored as base64 in the spreadsheet's JSON configuration by default, increasing server storage needs. So, saving images in an S3 bucket or similar external storage is recommended. This section brings more information about intercepting the image upload on the back end and in the front end.

Intercepting the Upload

Server

Use the beforeChange event to intercept image uploads on the server. This event lets you overwrite the image src, upload it to the appropriate service, and update its reference with a valid URL before saving it to the database.

Client

When images are stored externally, user requests should be redirected to the appropriate service with the necessary authentication token. Use the onbeforeloadimage event to intercept the download, enabling the correct image download.

Examples

Save images to the server's disk

In this example, images are saved to a public folder on the server, making them accessible as static files.

const server = require('@jspreadsheet/server');
const adapter = require('@jspreadsheet/redis-adapter');
const fs = require('fs');
const path = require('path');

// Helper function to save the image to disk
function saveImageToDisk(base64Image, imageName) {
    // Strip metadata from the base64 string
    const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, '');
    // Define the directory where images will be saved
    const imageDir = path.join(__dirname, 'public', 'images');
    // Ensure the directory exists
    if (! fs.existsSync(imageDir)) {
        fs.mkdirSync(imageDir, { recursive: true });
    }
    // Full path for the image
    const imagePath = path.join(imageDir, imageName);
    // Write the image to the file system
    fs.writeFileSync(imagePath, base64Data, { encoding: 'base64' });
    // Return the URL of the image to be saved in the spreadsheet
    return `/images/${imageName}`;
}

// Jspreadsheet license
const license = {
    clientId: '356a192b7913b04c54574d18c28d46e6395428ab',
    licenseKey: 'YOUR_LICENSE_KEY'
};

// Create the Server
server({
    config: {
        cors: {
            origin: "*"
        },
    },
    port: 3000,
    error: function(e) {
        console.error(e);
    },
    auth: async function(query) {
        return true;
    },
    load: async function(guid) {
        return await adapter.load(guid);
    },
    beforeChange: async function(guid, changes, query) {
        // Check for image upload event
        if (query && query.guid) {
            // Check for the correct event that upload images
            if (changes.method === 'setMedia' && Array.isArray(changes.args[0])) {
                // Go through all images
                for (const v of changes.args[0]) {
                    if (v.src && v.src.startsWith('data:')) {
                        // Generate a unique name for the image
                        const imageName = `image_${Date.now()}.png`;
                        // Save the image to disk
                        const imageUrl = saveImageToDisk(v.src, imageName);
                        // Replace the base64 src with the image URL
                        v.src = imageUrl;
                    }
                }
            }
        }

        return changes;
    },
    change: async function(guid, changes, query, onerror) {
        return await adapter.change(guid, changes, query, onerror);
    },
    create: async function(guid, config, query) {
        return await adapter.create(guid, config, query);
    },
    destroy: async function(guid, query) {
        return await adapter.destroy(guid, query);
    },
    license: license
});

Using MongoDB and S3

This example shows how to intercept and save images to S3 when using the MongoDB adapter. If you're not using MongoDB, you can upload images directly to your S3 bucket and update the image URL accordingly.

Server-side

Intercept the setMedia event to upload base64 images to S3.

const api = require('@jspreadsheet/api');
const server = require('@jspreadsheet/server');
const adapter = require('@jspreadsheet/mongodb-adapter');

// Jspreadsheet license
const license = {
    clientId: '356a192b7913b04c54574d18c28d46e6395428ab',
    licenseKey: 'YOUR_LICENSE_KEY'
}

// Define S3 configuration
api({
    s3: {
        key: "your-key",
        secret: "your-secret",
        bucket: "your-bucket",
        region: "us-your-region",
        url: "https://bucketname.s3.amazonaws.com/",
    },
    adapter: adapter
})

server({
    config: {
        cors: {
            origin: "*"
        },
    },
    port: 3000,
    error: function(e) {
        console.error(e)
    },
    auth: async function(query) {
        return true;
    },
    load: async function(guid) {
        return await adapter.load(guid);
    },
    beforeChange: async function(guid, changes, query) {
        if (query && query.guid) {
            if (changes.method === 'setMedia' && Array.isArray(changes.args[0])) {
                for (const v of changes.args[0]) {
                    if (v.src && v.src.startsWith('data:')) {
                        // Upload to S3
                        let ret = await api.setImage(guid, v.src);
                        if (ret) {
                            v.src = ret;  // Overwrite base64 with the S3 URL
                        }
                    }
                }
            }
        }

        return test;
    },
    change: async function(guid, changes, query, onerror) {
        return await adapter.change(guid, changes, query, onerror);
    },
    create: async function(guid, config, query) {
        return await adapter.create(guid, config, query);
    },
    destroy: async function(guid, query) {
        return await adapter.destroy(guid, query);
    },
    license: license,
    extensions: { api },
});

Client-side

Now, the image URLs will point to a private S3 route. The client intercepts the image load to call the API with the authentication bearer and retrieve the image from S3.

intrasheets({
    client: {
        url: 'http://localhost:3009',
        token: token,
        onbeforeloadimage: function (worksheet, img, options) {
            if (options.src.includes('/api')) {
                fetch('http://localhost:3009' + options.src, {
                    headers: {
                        'Authorization': 'Bearer ' + token
                    }
                })
                .then(response => response.blob())
                .then(blob => {
                    img.src = URL.createObjectURL(blob);  // Set the image source to the blob URL
                });

                return false;  // Prevent default image load until the API call completes
            }
        },
    }
});