Incorporating S3 Upload With An Existing HTML Form

If you’ve been able to incorporate the client-side S3 Uploader with a form that captures additional user information, please share your approach. The challenges I’m facing include:

  • keeping track of s3uploader1.files properties client-side–they are removed from this object after each upload completes,
  • submitting the form which contains additional user info, and including the file’s final URL, into the app server’s database,
  • Having to manage unique file names and folder paths client-side before submitting the S3 Uploader,
  • Using a stylized dropzone-like UI instead of Wappler’s S3 Upload UI. It’s very difficult to override Wappler’s styles, as well as undesirable in CSS.

What I’m trying to accomplish:

Incorporate S3 signed upload into an existing upload form, to stop storing videos on the application server and into DO’s Spaces using CDS services. This means I have an existing upload.ejs page that works, and is stylized, but now needs to incorporate the S3 client-side uploader into the flow. I’m facing all the issues above, as well as trying to understand the required order of events (flow, execution), data structures that need to be created and managed, etc.

I’m going to try and document how I tackled each one of these challenges. First, and probably the easiest, was solving for using my own stylized element to capture the files for S3 upload. Here’s what the element looks like in my page:

To accomplish this, my stylized element and the S3 Upload component use a bit of CSS trickery. The S3 Upload component sits on top of my stylized component, but it has an opacity of 0. This allows clicks to be captured by the S3 Upload to open the file browser. After files are selected, I know the S3 Upload component’s files data structure is populated so I use a repeat in my element to list the files. Once files are selected, my element updates to look like this:

Here’s the HTML:

                  <!-- Dropzone like file selector with S3 Upload Component -->
						<div class="form-group-modern">
							<label for="s3upload1">Your Files</label>
							<div id="file-drop-zone" class="file-drop-zone">
								<div is="dmx-if" dmx-bind:condition="!s3upload1.files.length">
									<span class="material-symbols-outlined">cloud_upload</span>
									<p>Choose files or drag here</p>
									<small>Up to 3 photos or videos.</small>
								</div>
								<div is="dmx-if" dmx-bind:condition="s3upload1.files.length">
									<div class="selected-files-container">
										<h6 class="selected-files-header">Selected Files:</h6>
										<ul class="file-list">
											<div dmx-repeat:repeatSelectedFiles="s3upload1.files">
												<li class="file-list-item">
													<span class="file-name" dmx-text="name"></span>
												</li>
											</div>
										</ul>
									</div>
								</div>
								<div id="s3upload1" is="dmx-s3-upload-multi" url="/api/utils/generateSignedUploadUrl" accept="image/*, video/*" dmx-on:success="submitvideoform.inp_videoFile.setValue(sc_generateSignedUrl.data.signedUrl);notifiesfanmain.success('S3 Upload Success')" dmx-on:error="notifiesfanmain.warning('S3 Upload Error: '+lastError)">
									<table class="table table-bordered">
										<tr dmx-hide="files.length">
											<td>
												No files selected
											</td>
										</tr>
										<tr dmx-show="files.length" dmx-repeat="files">
											<td width="50">
												<img width="50" dmx-bind:src="dataUrl" dmx-show="ready">
											</td>
											<td>
												<div>{{ name }}</div>
												<small dmx-show="!uploading && !error">Ready to Upload</small>
												<small dmx-show="!uploading && error" class="text-danger">{{ error }}</small>
												<div class="progress" dmx-show="uploading">
													<div class="progress-bar" dmx-style:width="percent+'%'"></div>
												</div>
											</td>
										</tr>
									</table>
									<p dmx-hide="state.uploading">
										<button class="btn btn-primary" dmx-on:click.stop="s3upload1.select()" dmx-show="state.idle">Browse</button>
										<button class="btn btn-primary" dmx-on:click.stop="s3upload1.upload()" dmx-show="state.ready">Upload</button>
										<button class="btn btn-danger" dmx-on:click.stop="s3upload1.reset()" dmx-show="state.done">Reset</button>
									</p>
									<p dmx-show="state.uploading">
										<button class="btn btn-danger" dmx-on:click.stop="s3upload1.abort()">Abort</button>
									</p>
								</div>
							</div>
						</div>
						<!-- END Dropzone and S3 Upload Integration -->

And the related CSS:

.file-drop-zone {
		position: relative;
		cursor: pointer;
		border: 2px dashed #ddd;
		border-radius: 12px;
		padding: 15px;
		text-align: center;
		transition: background-color 0.2s ease, border-color 0.2s ease;
	}

#s3upload1 {
		position: absolute;
		top: 0;
		left: 0;
		width: 100%;
		height: 100%;
		opacity: 0;
		cursor: pointer;
		z-index: 20;
	}

I’ve now started to integrate the S3 Upload with submitting the rest of the form. On success of the S3 Upload, I’m setting the file’s S3 URL into the form’s input videoFile. Previously, the form would submit an array of videoFile[ ] , but since S3 Upload runs and returns once for each video uploaded, that array goes away. I want to submit the form each time the S3 Upload is successful because I want one row with file and user info per file uploaded in my app DB. Triggering that flow is what I’m working on now.

Caution: when using the S3 Sign Upload Template in server connect, do not change the name of 'url' in the Set Value step. This will cause your client-side S3 uploader to fail. I renamed it and found out the hard way.

2 Likes