Custom System Via Client Side Scripting
In my digital travels, I've come across tons of sites. On a vast majority of these sites, I've thought to myself, "I know there's a better way to do XYZ."
I see text that is absurdly small, thumbnails that are either too small or far too large, or single file uploads where you really need batch file uploading, etc. I don't like being stuck with a format that doesn't make sense.
In this regard, I've come to love client-side scripting as a means of enhancing a website's functionality when I don't have access to the server code — i.e. I don't own the server, I just use their software for my needs.
I've done cosmetic overrides on sites before to enhance text, shrink/magnify thumbnails, etc. But, one site in particular, that I use at my workplace, has a particularly frustrating single file upload system that was just plain inefficient.
It only received one file at a time and required the user to always specify what columns information was in in a CSV, with no means of recording this data for later use.
I had an idea one night to inject my own multiple, automatic file uploader onto their site. I decided to make a Chrome extension that would detect when I was on their site and on that particular page.
I hadn't yet injected my own system into a system where I couldn't modify the server code. I guess I hadn't really thought about it before then. So, I formulated a game plan.
I used Fiddler by Telerik to analyze the request/responses the website sent to the server when I used the file uploader. I realized that the request sent to the server was just a bunch of form URL encoded data. As long as I provided it with my current session ID, I could send as many requests as I wished for one time.
So, in the Chrome extension, I created an input element of type "file," with the attribute of multiple to be able to select multiple files. When I'm adding in elements that don't exist, I tend to go the extra mile to make sure they have unique names that won't conflict with any elements already on the page.
<input type="file" id="multifiles-3342789a" multiple>
I made a change event listener that listened for the file input to select new files. Once files are selected, I created a loop that runs through the list of files, making the appropriate FormData appends and an XMLHttpRequest for each file.
By wrapping the loadstart and load listeners for each XMLHttpRequest in a closure, I could then give an active status report on when a file is uploading and when it is done doing so. I did this by also giving each status report its own uniquely generated ID.
//Create storage of newids
let newids = [];
//Add event listener for when the file input changes
document.querySelector('#multifiles-3342789a').addEventListener('change', function (e) {
//grab the files currently selected
let files = e.target.files;
//Store XHR requests
let xhr = [];
//Store the FormData for each request
let data = [];
//If there were files selected...
if (files.length > 0) {
//for each file in the files, different xhr requests with different form data
for (let i = 0; i < files.length; i++) {
//create carrier for filename
let filename = files[i].name;
//Generate a new id
let newid = Math.floor(Math.random() * 30000);
//if the new id is a part of the list, this loop will generate a new id until it's not a part of the list
while (newids.indexOf(newid) >= 0) {
newid = Math.floor(Math.random() * 30000);
}
//create reference to newid (needed only to prevent duplication)
newids[i] = newid;
//Create the FormData holder for this file
data[i] = new FormData();
//All of the formdata is appended here...
data[i].append('name-of-input', 'value-of-input');
//...
//Create the new XMLHttpRequest
xhr[i] = new XMLHttpRequest();
//Create closure for beginning of transfer
(function (i, filename, id) {
xhr[i].upload.addEventListener('loadstart', function () {
let self = this;
//store the id for this request
this.id = id;
//show that the uploader is loading (I'm using a div with the id shown below)
document.querySelector('#myalertdiv-3abc99z').innerHTML += `<div id="upload-${self.id}"><span style="color:blue;">${self.filename} is uploading...</span><br></div>`;
});
})(i, filename, newids[i]);
//Closure for loaded xhr request
(function(i, filename, id) {
xhr[i].addEventListener('load', function (e) {
let self = this;
//store the id for this request
this.id = id;
//store the filename
this.filename = filename;
document.querySelector(`#upload-${self.id}`).innerHTML = `<span style="color:green;">${self.filename} uploaded!</span><br>`;
window.setTimeout(function () {
//target the uploaded div and remove it after 5 seconds
document.getElementById(`upload-${self.id}`).remove();
}, 5000);
});
})(i, filename, newids[i]);
//Open the connection for transfer
xhr[i].open('POST', 'http://myprocessingurl.com/page.something', true);
//Send the form data
xhr[i].send(data[i]);
}
}
});
The original single file uploader on their site made you click an OK button after doing everything else when the file had finished uploading.
Again, I didn't like that. This new system I had created only required that I select the files I needed, and they would be automatically uploaded.
When you have to upload 10 or more files, it takes the wait time down to just a second to upload, compared to around 15-20 minutes using their single file uploader.
The entirety of that script was written in plain JavaScript, no libraries. The script was written specifically for Chrome so I could use the enhanced capabilities of ECMAScript 6 to get the work done easier and faster without having to worry about cross site compatibility.
I still use this script at my workplace and have added more implementations, such as cosmetic overrides and autoclickers. All of this and more is possible with client-side scripting!
You, too, can create your own custom system on a site that you don't directly control with some JavaScript knowledge and a will to succeed.