Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

stevegoddard14's avatar

Javascript File Upload Function within Dynamically Loaded Livewire Component

I have a livewire component that loads another component when the user clicks a link. This all works fine.

<livewire:dynamic-component :component="$details['livewire']" :key="$details['id']" :details="$details" />

There are a few different components that can be loaded. One of these has a drag and drop file input to allow the user to upload a file. This relies on a javascript function. If I load the component with the drag and drop directly, everything works fine. But if i load it dynamically (as i need to), i get a bunch of errors:

Alpine Expression Error: dragDrop is not defined
Alpine Expression Error: uploadProgress is not defined

I've tried putting the javascript within a @script section - but this has made no difference.

My livewire html:

<div>

  <div x-data="dragDrop()" class="col-md-12 mb-2">

    <div x-on:dragover.prevent="dragover" x-on:dragleave.prevent="dragleave" x-on:drop.prevent="drop($event)" class="card border-0">
      <div class="card-body" style=" border: dashed 4px #999; border-radius: 20px;">
        <div style="text-align: center;">

          <div class="d-flex justify-content-between">
            <div class="pl-3">
              <h4>Drop file here to upload or click the button to browse.</h4>
            </div>
          </div>
          
          <label for="upload">
            <span class="btn btn-md btn-primary text-white"><i class="fa-solid fa-file-import me-1" aria-hidden="true"></i> Browse for Files</span>
            <input wire:model="files" multiple type="file" id="upload" style="display:none">
          </label>

        </div>
      </div>
    </div>

    <div class="progress mt-2">
      <div class="progress-bar" role="progress-bar" :style="{width:uploadProgress+'%'}" aria-valuemin="0" aria-valuemax="100" x-text="uploadProgress+'%'"></div>
    </div>

  </div>

</div>

@script
  <script type="text/javascript">
    function dragDrop() {
      return  {
        uploadProgress:0,
        dragover() {
          // alert('Dragged over');
        },
        dragleave() {
          // alert('Drag leave');
        },
        drop(e) {
          // alert('Dropped');
          if(e.dataTransfer.files.length > 0) {
            const files = e.dataTransfer.files
            this.uploadFiles(files)
          }

        },
        uploadFiles(files) {
          this.uploadProgress = 0; //Reset?
          @this.uploadMultiple('files', files, 
            (success) => {
              //
            },
            (error) => {
              //
            },
            (event) => {
              this.uploadProgress = event.detail.progress
            }
          )
        }
      }
    }
  </script>
@endscript

Any help gratefully received!

Thanks

0 likes
2 replies
LaryAI's avatar
Level 58

It seems like you're encountering issues with Alpine.js not being able to find the dragDrop function when the Livewire component is loaded dynamically. This can happen because the script may not be evaluated when the component is dynamically inserted into the DOM.

To solve this, you can ensure that the JavaScript function is globally available before the Livewire component is initialized. You can do this by including the JavaScript function in your main layout file or in a separate JavaScript file that is loaded when the page loads.

Here's how you can modify your code to make the dragDrop function globally available:

  1. Move the dragDrop function into a separate JavaScript file or a <script> tag in your main layout. For example, create a new file called drag-drop.js:
window.dragDrop = function() {
  return {
    uploadProgress: 0,
    dragover() {
      // alert('Dragged over');
    },
    dragleave() {
      // alert('Drag leave');
    },
    drop(e) {
      // alert('Dropped');
      if (e.dataTransfer.files.length > 0) {
        const files = e.dataTransfer.files;
        this.uploadFiles(files);
      }
    },
    uploadFiles(files) {
      this.uploadProgress = 0; // Reset?
      // Note: You'll need to handle the Livewire upload logic here.
    }
  };
};
  1. Include this file in your main layout so that it's loaded when the page loads:
<script src="/path/to/drag-drop.js"></script>
  1. In your Livewire component, you can now reference the dragDrop function without needing to define it within the component itself:
<div x-data="dragDrop()" class="col-md-12 mb-2">
  <!-- ... -->
</div>

By doing this, the dragDrop function is defined on the window object and is available globally, which means it should be accessible to Alpine.js when your Livewire component is dynamically loaded.

Remember to handle the Livewire file upload logic appropriately, as the @this.uploadMultiple directive you have in your original code is specific to Livewire and needs to be integrated with the JavaScript function in a way that Livewire can process the file uploads. You may need to use Livewire's JavaScript API to trigger file uploads from your Alpine.js component.

stevegoddard14's avatar

Thanks @laryai . That partially solves the issue. I've moved the dragDrop function to my app.js file and sure enough, i dont get the error on load. But the upload doesn't work.

However, i notice in your reply that you've removed this (vital) section from the function:

  @this.uploadMultiple('files', files, 
    (success) => {
      //
    },
    (error) => {
      //
    },
    (event) => {
      this.uploadProgress = event.detail.progress
    }
  )

When i put this back into the function, i'm unable to compile my javascript. So how can i handle/call the actual upload?

Please or to participate in this conversation.