Codementor Events

Custom System Via Client Side Scripting

Published Jan 10, 2018Last updated Sep 25, 2023
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.

Discover and read more posts from Marcus Parsons
get started
post commentsBe the first to share your opinion
Show more replies