All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dominokit.domino.ui.upload.FileUpload Maven / Gradle / Ivy

/*
 * Copyright © 2019 Dominokit
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.dominokit.domino.ui.upload;

import static java.util.Objects.nonNull;
import static org.jboss.elemento.Elements.div;
import static org.jboss.elemento.Elements.input;

import elemental2.dom.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import jsinterop.base.Js;
import org.dominokit.domino.ui.grid.Column;
import org.dominokit.domino.ui.grid.Row;
import org.dominokit.domino.ui.grid.Row_12;
import org.dominokit.domino.ui.icons.BaseIcon;
import org.dominokit.domino.ui.notifications.Notification;
import org.dominokit.domino.ui.utils.BaseDominoElement;
import org.dominokit.domino.ui.utils.DominoElement;
import org.dominokit.domino.ui.utils.HasName;
import org.jboss.elemento.IsElement;

/**
 * A component for uploading files (photos, documents or any other file type) from local storage
 *
 * @see BaseDominoElement
 * @see HasName
 */
public class FileUpload extends BaseDominoElement
    implements HasName {

  private final Row row = Row.create();
  private int thumbSpanXLarge = 2;
  private int thumbSpanLarge = 2;
  private int thumbSpanMedium = 4;
  private int thumbSpanSmall = 6;
  private int thumbSpanXSmall = 12;

  private int thumbOffsetXLarge = -1;
  private int thumbOffsetLarge = -1;
  private int thumbOffsetMedium = -1;
  private int thumbOffsetSmall = -1;
  private int thumbOffsetXSmall = -1;

  private final DominoElement formElement =
      DominoElement.of(div()).css("file-upload");
  private final DominoElement uploadMessageContainer =
      DominoElement.of(div()).css("file-upload-message");
  private final DominoElement uploadIconContainer =
      DominoElement.of(div()).css("file-upload-message-icon");
  private HTMLInputElement hiddenFileInput;
  private final DominoElement filesContainer = DominoElement.of(div());
  private final List addedFileItems = new ArrayList<>();
  private double maxFileSize;
  private String url;

  private final List onAddFileHandlers = new ArrayList<>();
  private boolean autoUpload = true;
  private boolean singleFile = false;
  private String errorMessage = "Only one file is allowed to be uploaded";

  private Supplier> successCodesProvider =
      () -> Arrays.asList(200, 201, 202, 203, 204, 205, 206, 207, 208, 226);

  private UploadRequestSender requestSender = (XMLHttpRequest::send);

  private Optional dropEffect = Optional.empty();

  public FileUpload() {
    uploadMessageContainer.appendChild(uploadIconContainer);
    createHiddenInput();
    formElement.appendChild(uploadMessageContainer);
    formElement.appendChild(filesContainer);

    hiddenFileInput.addEventListener(
        "change",
        evt -> {
          uploadFiles(hiddenFileInput.files);
        });
    uploadMessageContainer.addEventListener("click", evt -> hiddenFileInput.click());
    formElement.addEventListener(
        "drop",
        evt -> {
          FileList files = ((DragEvent) evt).dataTransfer.files;
          if (dropEffect.isPresent() && files.length > 0) {
            ((DragEvent) evt).dataTransfer.dropEffect = dropEffect.get().getEffect();
          }
          if (!singleFile || files.length == 1) {
            uploadFiles(files);
          } else {
            notifySingleFileError();
          }
          removeHover();
          evt.stopPropagation();
          evt.preventDefault();
        });
    formElement.addEventListener(
        "dragover",
        evt -> {
          dropEffect.ifPresent(
              effect -> ((DragEvent) evt).dataTransfer.dropEffect = effect.getEffect());
          addHover();
          evt.stopPropagation();
          evt.preventDefault();
        });
    formElement.addEventListener(
        "dragleave",
        evt -> {
          dropEffect.ifPresent(
              effect -> ((DragEvent) evt).dataTransfer.dropEffect = effect.getEffect());
          if (isFormUploadElement(evt.target)) removeHover();
          evt.stopPropagation();
          evt.preventDefault();
        });
    filesContainer.appendChild(row.element());
    init(this);
  }

  /**
   * Sets the sender of the upload request when the request is ready to be sent
   *
   * @param requestSender the {@link UploadRequestSender}
   * @return same instance
   */
  public FileUpload setRequestSender(UploadRequestSender requestSender) {
    if (nonNull(requestSender)) {
      this.requestSender = requestSender;
    }
    return this;
  }

  private void notifySingleFileError() {
    Notification.createWarning(errorMessage).show();
  }

  private boolean isFormUploadElement(EventTarget target) {
    HTMLElement element = Js.uncheckedCast(target);
    return element == formElement.element();
  }

  private void addHover() {
    formElement.addCss("file-upload-hover");
  }

  private void uploadFiles(FileList files) {
    if (singleFile) {
      addedFileItems.forEach(FileItem::remove);
      addedFileItems.clear();
    }
    for (int i = 0; i < files.length; i++) {
      File file = files.item(i);
      addFilePreview(file);
    }
    hiddenFileInput.value = "";
  }

  /** Sends the requests for uploading all files */
  public void uploadAllFiles() {
    addedFileItems.forEach(fileItem -> fileItem.upload(requestSender));
  }

  private void addFilePreview(File file) {
    FileItem fileItem =
        FileItem.create(file, new UploadOptions(url, maxFileSize, successCodesProvider));
    Column previewColumn =
        Column.span(
                thumbSpanXLarge, thumbSpanLarge, thumbSpanMedium, thumbSpanSmall, thumbSpanXSmall)
            .appendChild(fileItem.element());

    if (thumbOffsetXLarge >= 0) {
      previewColumn.onXLargeOffset(Column.OnXLargeOffset.of(thumbOffsetXLarge));
    }
    if (thumbOffsetLarge >= 0) {
      previewColumn.onLargeOffset(Column.OnLargeOffset.of(thumbOffsetLarge));
    }
    if (thumbOffsetMedium >= 0) {
      previewColumn.onMediumOffset(Column.OnMediumOffset.of(thumbOffsetMedium));
    }
    if (thumbOffsetSmall >= 0) {
      previewColumn.onSmallOffset(Column.OnSmallOffset.of(thumbOffsetSmall));
    }
    if (thumbOffsetXSmall >= 0) {
      previewColumn.onXSmallOffset(Column.OnXSmallOffset.of(thumbOffsetXSmall));
    }

    row.appendChild(previewColumn);
    addedFileItems.add(fileItem);

    fileItem.addRemoveHandler(
        removedFile -> {
          previewColumn.element().remove();
          addedFileItems.remove(fileItem);
        });

    onAddFileHandlers.forEach(handler -> handler.onAddFile(fileItem));

    if (fileItem.isCanceled()) {
      fileItem.remove();
    }

    if (autoUpload && !fileItem.isCanceled() && !fileItem.isRemoved()) {
      fileItem.upload(requestSender);
    }
  }

  private void removeHover() {
    formElement.removeCss("file-upload-hover");
  }

  private void createHiddenInput() {
    hiddenFileInput =
        input("file")
            .style(
                "visibility: hidden; position: absolute; top: 0px; left: 0px; height: 0px; width: 0px;")
            .element();
    DomGlobal.document.body.appendChild(hiddenFileInput);
  }

  /** @return new instance */
  public static FileUpload create() {
    return new FileUpload();
  }

  /** {@inheritDoc} */
  @Override
  public HTMLDivElement element() {
    return formElement.element();
  }

  /** {@inheritDoc} */
  @Override
  public FileUpload appendChild(Node child) {
    uploadMessageContainer.appendChild(child);
    return this;
  }

  /** {@inheritDoc} */
  @Override
  public FileUpload appendChild(IsElement child) {
    return appendChild(child.element());
  }

  /**
   * Enables multiple files upload
   *
   * @return same instance
   */
  public FileUpload multipleFiles() {
    hiddenFileInput.multiple = true;
    this.singleFile = false;
    return this;
  }

  /**
   * Enables uploading only a single file
   *
   * @return same instance
   */
  public FileUpload singleFile() {
    hiddenFileInput.multiple = false;
    this.singleFile = true;
    return this;
  }

  /**
   * Sets the accepted files extensions
   *
   * @param acceptedFiles a comma separated string containing all the accepted file extensions
   * @return same instance
   */
  public FileUpload accept(String acceptedFiles) {
    hiddenFileInput.accept = acceptedFiles;
    return this;
  }

  /**
   * Sets the maximum accepted file size
   *
   * @param maxFileSize the maximum size of the file
   * @return same instance
   */
  public FileUpload maxFileSize(double maxFileSize) {
    this.maxFileSize = maxFileSize;
    return this;
  }

  /**
   * Sets the url for uploading the files to
   *
   * @param url the url of the server
   * @return same instance
   */
  public FileUpload setUrl(String url) {
    this.url = url;
    addedFileItems.forEach(fileItem -> fileItem.setUrl(url));
    return this;
  }

  /**
   * Sets a handler to be called when file is added
   *
   * @param onAddFileHandler a {@link OnAddFileHandler}
   * @return same instance
   */
  public FileUpload onAddFile(OnAddFileHandler onAddFileHandler) {
    onAddFileHandlers.add(onAddFileHandler);
    return this;
  }

  /**
   * Sets that the file should be automatically uploaded when the user adds it
   *
   * @return same instance
   */
  public FileUpload autoUpload() {
    this.autoUpload = true;
    return this;
  }

  /**
   * Sets that the file should be uploaded only when calling {@link
   * FileUpload#uploadFiles(FileList)} or {@link FileUpload#uploadAllFiles()}
   *
   * @return same instance
   */
  public FileUpload manualUpload() {
    this.autoUpload = false;
    return this;
  }

  /** @return the files container */
  public Row getRow() {
    return row;
  }

  /** @return The form element */
  public DominoElement getFormElement() {
    return DominoElement.of(formElement);
  }

  /** @return the upload message container */
  public DominoElement getUploadMessageContainer() {
    return DominoElement.of(uploadMessageContainer);
  }

  /** @return the upload icon container */
  public DominoElement getUploadIconContainer() {
    return DominoElement.of(uploadIconContainer);
  }

  /** @return the hidden file input element */
  public DominoElement getHiddenFileInput() {
    return DominoElement.of(hiddenFileInput);
  }

  /** @return the files container */
  public DominoElement getFilesContainer() {
    return DominoElement.of(filesContainer);
  }

  /** @return the added file items */
  public List getAddedFileItems() {
    return addedFileItems;
  }

  /** @return the maximum accepted file size */
  public double getMaxFileSize() {
    return maxFileSize;
  }

  /** @return the url for the server */
  public String getUrl() {
    return url;
  }

  /** @return all {@link OnAddFileHandler} defined */
  public List getOnAddFileHandlers() {
    return onAddFileHandlers;
  }

  /** @return true if auto upload is set, false otherwise */
  public boolean isAutoUpload() {
    return autoUpload;
  }

  /** {@inheritDoc} */
  @Override
  public String getName() {
    return hiddenFileInput.name;
  }

  /**
   * Sets the upload icon
   *
   * @param icon a {@link BaseIcon}
   * @return same instance
   */
  public FileUpload setIcon(BaseIcon icon) {
    uploadIconContainer.appendChild(icon);
    return this;
  }

  /**
   * Sets the column span for a file preview
   *
   * @param xLarge the span when the screen is X large
   * @param large the span when the screen is Large
   * @param medium the span when the screen is Medium
   * @param small the span when the screen is Small
   * @param xSmall the span when the screen is X small
   * @return same instance
   */
  public FileUpload setThumbSpans(int xLarge, int large, int medium, int small, int xSmall) {
    this.thumbSpanXLarge = xLarge;
    this.thumbSpanLarge = large;
    this.thumbSpanMedium = medium;
    this.thumbSpanSmall = small;
    this.thumbSpanXSmall = xSmall;
    return this;
  }

  /**
   * Sets the column offset for a file preview
   *
   * @param xLarge the offset when the screen is X large
   * @param large the offset when the screen is Large
   * @param medium the offset when the screen is Medium
   * @param small the offset when the screen is Small
   * @param xSmall the offset when the screen is X small
   * @return same instance
   */
  public FileUpload setThumbOffset(int xLarge, int large, int medium, int small, int xSmall) {
    this.thumbOffsetXLarge = xLarge;
    this.thumbOffsetLarge = large;
    this.thumbOffsetMedium = medium;
    this.thumbOffsetSmall = small;
    this.thumbOffsetXSmall = xSmall;
    return this;
  }

  /**
   * Sets the success codes for upload files
   *
   * @param successCodesProvider a {@link Supplier} for getting the success codes
   */
  public void setSuccessCodesProvider(Supplier> successCodesProvider) {
    this.successCodesProvider = successCodesProvider;
  }

  /** {@inheritDoc} */
  @Override
  public FileUpload setName(String name) {
    hiddenFileInput.name = name;
    return this;
  }

  /**
   * Sets the error message when uploading file fails
   *
   * @param errorMessage the error message
   * @return same instance
   */
  public FileUpload setSingleFileErrorMessage(String errorMessage) {
    this.errorMessage = errorMessage;
    return this;
  }

  /** @return the {@link DropEffect} configured */
  public Optional getDropEffect() {
    return dropEffect;
  }

  /**
   * Sets the drop effect
   *
   * @param dropEffect the {@link DropEffect}
   * @return same instance
   */
  public FileUpload setDropEffect(DropEffect dropEffect) {
    if (nonNull(dropEffect)) {
      this.dropEffect = Optional.of(dropEffect);
    }
    return this;
  }

  /** A handler to be called when file is added */
  @FunctionalInterface
  public interface OnAddFileHandler {
    /** @param fileItem the added {@link FileItem} */
    void onAddFile(FileItem fileItem);
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy