pqina / filepond

🌊 A flexible and fun JavaScript file upload library
https://pqina.nl/filepond
MIT License
15.15k stars 825 forks source link

Image caption input #208

Closed clementmas closed 5 years ago

clementmas commented 5 years ago

Hi @rikschennink, great work with this modern file upload library!

I need to allow users to add a caption/description for each photo uploaded. I looked into creating a custom plugin to add an input below each file but it looks like quite an undertaking.

Would you have any pointers on how to go about this? Or is there already some code out there I could use?

Thanks! Clement

rikschennink commented 5 years ago

Thanks @clementmas !

I think the best approach would be to fork the image-preview plugin and then make alterations there. It should be relatively easy to add an input field to the HTML, you could use the setMetadata method on the item to add data to each file.

sbritz commented 1 year ago

Hi, Rik, I would like to add a caption-input field to any item, too, but I cannot find the HTML in your image-preview-plugin, where I could put it in. Can you perhaps help me? I'm quite a newbie in JS and don't understand it. Thx a lot! And by the way, awesome job with filepond. We will buy your Pintura-Plugin, soon.

Greetings Stefan

rikschennink commented 1 year ago

@sbritz This is where in the poster plugin (which is used with Pintura) the image element is added, it's similar for the image preview plugin: https://github.com/pqina/filepond-plugin-file-poster/blob/master/src/js/view/createPosterView.js#L14-L15

bilogic commented 1 year ago

@clementmas did you manage to find an easy way?

clementmas commented 1 year ago

I do have a working solution. This code adds <input name="captions[]" /> fields for all the uploaded items:

/* filepond-plugin-image-caption.js */
import { FileStatus } from 'filepond';

/**
 * FilePond image caption plugin
 *
 * FilePondPluginImageCaption 1.0.3
 * Author: clementmas
 * Licensed under MIT, https://opensource.org/licenses/MIT
 */
export default function ({ addFilter, utils }) {
    const { Type, createRoute, createView } = utils;

    // Called when a new file is added
    addFilter('CREATE_VIEW', function (viewAPI) {
        const { is, view, query } = viewAPI;

        // Make sure the option `addImageCaption` is enabled
        if (!query('GET_ADD_IMAGE_CAPTION')) return;

        // Skip invalid file types
        if (!is('file')) return;

        function onItemAdded({ root, props: { id } }) {
            const item = query('GET_ITEM', id);

            // Item could theoretically have been removed in the mean time
            if (!item || item.archived) return;

            const value = item.getMetadata('caption');

            const isInvalid = item.status === FileStatus.LOAD_ERROR;

            // Append image caption input
            root.ref.imagePreview = view.appendChildView(
                view.createChildView(
                    createView(addCaptionInputField(value, isInvalid)),
                    {
                        id,
                    },
                ),
            );

            // Disable file action buttons tabindex (cancel, revert, etc.)
            // to easily tab from one caption input to another
            view.element
                .querySelectorAll('button')
                .forEach((button) => button.setAttribute('tabindex', -1));
        }

        view.registerWriter(
            createRoute({
                DID_INIT_ITEM: onItemAdded,
            }),
        );
    });

    // Plugin config options
    return {
        options: {
            // Enable or disable image captions
            addImageCaption: [true, Type.BOOLEAN],

            // Input placeholder
            imageCaptionPlaceholder: [null, Type.STRING],

            // Input max length
            imageCaptionMaxLength: [null, Type.INT],
        },
    };
}

// Create DOM input
function addCaptionInputField(value, isInvalid) {
    return {
        name: 'image-caption-input',
        tag: 'input',
        ignoreRect: true,
        create: function create({ root }) {
            // Input name
            root.element.setAttribute('name', 'captions[]');

            // Value
            if (value) {
                root.element.value = value;
            }

            // Placeholder
            const placeholder = root.query('GET_IMAGE_CAPTION_PLACEHOLDER');
            if (placeholder) {
                root.element.setAttribute('placeholder', placeholder);
            }

            // Max length
            const maxLength = root.query('GET_IMAGE_CAPTION_MAX_LENGTH');
            if (maxLength) {
                root.element.setAttribute('maxlength', maxLength);
            }

            // Autocomplete off
            root.element.setAttribute('autocomplete', 'off');

            // Visually hide the element if the file is invalid but keep the input
            // to make sure the "captions[]" index will stay in sync with the FilePond photos
            if (isInvalid) {
                root.element.classList.add('image-caption-input-invalid');
            }

            // Prevent Enter key from submitting form
            root.element.addEventListener('keydown', function (e) {
                if (e.key === 'Enter') {
                    e.preventDefault();
                }
            });
        },
    };
}
/* filepond-plugin-image-caption.css */
input.filepond--image-caption-input {
    position: absolute;
    width: 100%;
    height: 38px;
    bottom: 0;
    left: 0;
    opacity: 0.7;
    padding: 0 10px;
    border: 0;
    outline: 0;
    border-bottom-left-radius: 4px;
    border-bottom-right-radius: 4px;
    z-index: 10;
    transition: opacity 0.2s;

    background: white;
    font-size: 14px;
    font-weight: normal;
    color: rgba(0, 0, 0, 0.87);
}

input.filepond--image-caption-input:focus {
    opacity: 0.9;
}

input.filepond--image-caption-input.image-caption-input-invalid {
    visibility: hidden;
    width: 0;
    height: 0;
    padding: 0;
}

Usage:

import * as FilePond from 'filepond';
import FilePondPluginImageCaption from './filepond-plugin-image-caption.js';
import 'filepond/dist/filepond.min.css';
import './filepond-plugin-image-caption.css';

// Register the plugin
FilePond.registerPlugin(FilePondPluginImageCaption);

FilePond.create(fileInputEl, {
    // ...

    // Caption (custom plugin)
    imageCaptionPlaceholder: 'Description...',
    imageCaptionMaxLength: 255,
});

Then I deal with the input values on the backend on form submission.

I considered creating an official plugin but I find the code quite messy. It's mostly boilerplate. I think FilePond v5 needs to seriously improve the plugin system.

bilogic commented 1 year ago

@clementmas wow thanks!

bilogic commented 1 year ago

@clementmas

~import './filepond-plugin-image-caption.css'; this line doesn't seem to have any effect, the input was visible for a moment while image was loading and disappeared after things settled. I'm not that familiar with JS, but is this how CSS should be imported?~

I was adding this in another project https://github.com/filamentphp/filament and managed to resolve the issue, thanks!

bilogic commented 1 year ago

@clementmas

I'm totally new to filepond and its API interface indeed looks hard to comprehend. Would you know of any examples to write the description into the exif of the images?

clementmas commented 1 year ago

Most people read the EXIF data but I don't know how or why you would write to it.

If you're just looking to associate the description to each uploaded file on the frontend, you can use the setMetadata method as Rik suggested.

However, if you're not familiar with JS, you should post your question on a support forum or hire someone.

rikschennink commented 1 year ago

I think FilePond v5 needs to seriously improve the plugin system.

It's going to be a lot easier.

bilogic commented 1 year ago

@clementmas

My reason for EXIF is due to the multiple "interfaces" I need to bridge: JS, filepond, Filament, Livewire, Laravel.

Having it in just JS will avoid the need to submit PRs which may not be accepted, and EXIFs are glued to the images.

Thanks anyway, your example was already of great help!

bilogic commented 1 year ago

Thanks @clementmas !

I think the best approach would be to fork the image-preview plugin and then make alterations there. It should be relatively easy to add an input field to the HTML, you could use the setMetadata method on the item to add data to each file.

@rikschennink

I'm looking into this. Would it mean that we can only add captions before uploading? Or can we also update captions of images that have already been uploaded?

bilogic commented 1 year ago

Most people read the EXIF data but I don't know how or why you would write to it.

If you're just looking to associate the description to each uploaded file on the frontend, you can use the setMetadata method as Rik suggested.

However, if you're not familiar with JS, you should post your question on a support forum or hire someone.

I managed to setMetadata('caption') on the file now. So here is the flow:

  1. File is uploaded to server
  2. Caption is typed in and setMetadata('caption', caption) is triggered
  3. Is there an API that I can ride on to transmit the captions to the server? Or do I have to perform my own AJAX?

Appreciate if someone can help me out with the lifecycle of filepond. Thank you.

elmudometal commented 1 month ago

@clementmas

~import './filepond-plugin-image-caption.css'; this line doesn't seem to have any effect, the input was visible for a moment while image was loading and disappeared after things settled. I'm not that familiar with JS, but is this how CSS should be imported?~

I was adding this in another project https://github.com/filamentphp/filament and managed to resolve the issue, thanks!

Hi friend, I would like to add this functionality to Filament and I see that you did it, can you help me? How did you implement it?

Alinawaz-786 commented 1 month ago

But I have faced one issue that I still haven't fixed. When I go to our Edit page, it loads very slowly. I debugged the code and found the issue with FilePond, which is used five times on the page to upload images in Laravel Livewire. I reduced the number of FilePond instances one by one, and the page became more efficient in terms of speed as I reduced them.

Do you have any ideas on how to fix this issue in Laravel?