A File-Splitting Utility in JavaScript

The JavaScript code below implements a simple file-splitting utility. It provides a way to split a large file into a number of smaller files, and then to re-join those smaller files back into the larger one. Splitting a large file up in this manner may make it easier to move the file from place to place in certain contexts. Notably, it may make it easier to send a large file as a series of email attachments.

To see the code in action, copy it into an .html file and open that file in a web browser that runs JavaScript. Or, for an online version, visit http://thiscouldbebetter.neocities.org/filesplitter.html.

filesplitter


<html>
<body>

<div id="divMain"></div>

<script type="text/javascript">

// main

function main()
{
	Globals.Instance.initialize();
}

// classes

// static classes

function ControlBuilder()
{
	// static class
}
{
	ControlBuilder.button = function(text, methodToRunOnClick, contextForMethod)
	{
		var returnValue = document.createElement("button");
		returnValue.innerHTML = text;
		returnValue.onclick = function(event)
		{
			methodToRunOnClick.call(contextForMethod, event);
		}
		return returnValue;
	}

	ControlBuilder.container = function(childControls, hasBorder)
	{
		var returnValue = document.createElement("div");
		for (var i = 0; i < childControls.length; i++)
		{
			var childControl = childControls[i];
			returnValue.appendChild(childControl);
		}
		if (hasBorder == true)
		{
			returnValue.style = "border:1px solid";
		}
		return returnValue;
	}

	ControlBuilder.fileUploader = function(methodToRunOnChange, contextForMethod)
	{
		var returnValue = document.createElement("input");
		returnValue.type = "file";
		returnValue.onchange = function(changeEvent)
		{
			methodToRunOnChange.call(contextForMethod, changeEvent);
		}
		return returnValue;
	}

	ControlBuilder.inputNumber = function(id, value, methodToRunOnChange, contextForMethod)
	{
		var returnValue = document.createElement("input");
		returnValue.id = id;
		returnValue.value = value;
		returnValue.type = "number";
		returnValue.onchange = function(event)
		{
			methodToRunOnChange.call(contextForMethod, event);
		}
		return returnValue;
	}

	ControlBuilder.inputText = function(id, value, isReadOnly)
	{
		var returnValue = document.createElement("input");
		returnValue.id = id;
		returnValue.value = value;
		returnValue.readonly = isReadOnly;
		return returnValue;
	}

	ControlBuilder.label = function(text)
	{
		var returnValue = document.createElement("label");
		returnValue.innerHTML = text;
		return returnValue;
	}

	ControlBuilder.toDOMElementsMany = function(controllables)
	{
		var returnValues = [];

		for (var i = 0; i < controllables.length; i++)
		{
			var controllable = controllables[i];
			var control = controllable.toDOMElement();
			returnValues.push(control);
		}

		return returnValues;
	}
}

function FileHelper()
{
	// static class
}
{
	FileHelper.loadFile = function(fileToLoad, callback, contextForCallback)
	{
		var fileReader = new FileReader();
		fileReader.systemFile = fileToLoad;
		fileReader.callback = callback;
		fileReader.contextForCallback = contextForCallback;
		fileReader.onload = FileHelper.loadFile_FileLoaded;
		fileReader.readAsBinaryString(fileToLoad);
	}

	FileHelper.loadFile_FileLoaded = function(fileLoadedEvent)
	{
		var fileReader = fileLoadedEvent.target;
		var contentsOfFileLoadedAsBinaryString = fileReader.result;
		var fileName = fileReader.systemFile.name;
		var fileLoaded = new File(fileName, contentsOfFileLoadedAsBinaryString);

		var callback = fileReader.callback;
		var contextForCallback = fileReader.contextForCallback;
		callback.call(contextForCallback, fileLoaded);
	}

	FileHelper.saveFile = function(fileToSave)
	{
		var fileAsByteString = fileToSave.contentsAsBinaryString;
		var fileAsArrayBuffer = new ArrayBuffer(fileAsByteString.length);
		var fileAsArrayUnsigned = new Uint8Array(fileAsArrayBuffer);
		for (var i = 0; i < fileAsByteString.length; i++) 
		{
			fileAsArrayUnsigned[i] = fileAsByteString.charCodeAt(i);
		}

		var fileAsBlob = new Blob([fileAsArrayBuffer], {type:'unknown/unknown'});

		var link = document.createElement("a");
		link.href = window.URL.createObjectURL(fileAsBlob);
		link.download = fileToSave.name;
		link.click();
	}
}

// instance classes

function File(name, contentsAsBinaryString)
{
	this.name = name;
	this.contentsAsBinaryString = contentsAsBinaryString;
}
{
	File.prototype.download = function()
	{
		FileHelper.saveFile(this);
	}

	File.prototype.toDOMElement = function()
	{
		var returnValue = ControlBuilder.container
		(
			[
				ControlBuilder.inputText(null, this.name),
				ControlBuilder.button("Download", this.download, this),
			]
		);

		returnValue.file = this;

		return returnValue;
	}
}

function FileSplittingSession()
{
	this.fileUnified = null;
	this.filesSplit = [];
	this.bytesPerFileSplit = 1000;
}
{
	FileSplittingSession.prototype.bytesPerFileSplit_Changed = function(event)
	{
		var inputBytesPerFileSplit = event.target;
		var bytesPerFileSplitAsString = inputBytesPerFileSplit.value;
		this.bytesPerFileSplit = parseInt(bytesPerFileSplitAsString);
	}

	FileSplittingSession.prototype.join = function()
	{
		if (this.filesSplit.length == 0)
		{
			alert("No files to join!");
			return;
		}

		var fileUnifiedAsBinaryString = "";

		var fileUnifiedName = this.filesSplit[0].name + "-Joined";

		for (var i = 0; i < this.filesSplit.length; i++)
		{
			var fileSplit = this.filesSplit[i];
			var fileSplitAsBinaryString = fileSplit.contentsAsBinaryString;
			fileUnifiedAsBinaryString += fileSplitAsBinaryString;
		} 

		this.fileUnified = new File
		(
			fileUnifiedName, fileUnifiedAsBinaryString
		);
		
		this.domElementUpdate();		
	}

	FileSplittingSession.prototype.fileSplitAdd = function(event)
	{
		var inputFileSplitToAdd = event.target;
		var systemFileToAdd = inputFileSplitToAdd.files[0];
		if (systemFileToAdd == null)
		{
			alert("No file specified!");
		}
		else
		{
			FileHelper.loadFile
			(
				systemFileToAdd, 
				this.fileSplitAdd_FileLoaded, 
				this
			)		
		}
	}

	FileSplittingSession.prototype.fileSplitAdd_FileLoaded = function(fileLoaded)
	{
		this.filesSplit.push(fileLoaded);
		this.domElementUpdate();
	}

	FileSplittingSession.prototype.fileUnifiedUpload = function(event)
	{
		var inputFileUnified = event.target;
		var systemFileUnified = inputFileUnified.files[0];
		if (systemFileUnified == null)
		{
			alert("No file specified!");
		}
		else
		{
			FileHelper.loadFile
			(
				systemFileUnified, 
				this.fileUnifiedUpload_FileLoaded, 
				this
			)
		}
	}

	FileSplittingSession.prototype.fileUnifiedUpload_FileLoaded = function(fileLoaded)
	{
		this.fileUnified = fileLoaded;
		this.domElementUpdate();
	}

	FileSplittingSession.prototype.split = function()
	{
		var fileToSplit = this.fileUnified;

		if (fileToSplit == null)
		{
			alert("No file to split!");
			return;
		}

		this.filesSplit.length = 0;

		var fileToSplitAsBinaryString = fileToSplit.contentsAsBinaryString;
		var numberOfBytesInFileToSplit = fileToSplitAsBinaryString.length;

		var numberOfFilesSplit = Math.ceil
		(
			numberOfBytesInFileToSplit / this.bytesPerFileSplit
		);
		
		var fileNameRoot = fileToSplit.name + "-";	

		for (var i = 0; i < numberOfFilesSplit; i++)
		{
			var byteIndexStart = i * this.bytesPerFileSplit;
			var byteIndexEnd = byteIndexStart + this.bytesPerFileSplit - 1;

			if (byteIndexEnd >= numberOfBytesInFileToSplit)
			{
				byteIndexEnd = numberOfBytesInFileToSplit - 1;
			}

			var fileSplitAsBinaryString = fileToSplitAsBinaryString.substring // not "substr"!
			(
				byteIndexStart,
				byteIndexEnd + 1
			);

			var fileName = 
				fileNameRoot + (i + 1) + "of" + numberOfFilesSplit;
			var fileNameAndContents = new File
			(
				fileName, fileSplitAsBinaryString
			);
			this.filesSplit.push(fileNameAndContents);
		}

		this.domElementUpdate();		
	}

	// dom

	FileSplittingSession.prototype.domElementUpdate = function()
	{
		var containerFileUnified = ControlBuilder.container
		(
			[
				ControlBuilder.label("File Unified:"),
				ControlBuilder.fileUploader(this.fileUnifiedUpload, this),
				(this.fileUnified == null ? ControlBuilder.label("[none]") : this.fileUnified.toDOMElement())
			],
			true // hasBorder
		);	

		var containerSplitAndJoin = ControlBuilder.container
		(
			[
				ControlBuilder.label("Bytes per Split:"),
				ControlBuilder.inputNumber
				(
					"inputBytesPerFileSplit", 
					this.bytesPerFileSplit, 
					this.bytesPerFileSplit_Changed, 
					this
				),
				ControlBuilder.button("Split", this.split, this),
				ControlBuilder.button("Join", this.join, this),
			],
			true // hasBorder
		);

		var containerFilesSplit = ControlBuilder.container
		(	
			[
				ControlBuilder.label("Files Split:"),
				ControlBuilder.fileUploader(this.fileSplitAdd, this),
				ControlBuilder.container
				(
					ControlBuilder.toDOMElementsMany(this.filesSplit)
				),
			],

			true // hasBorder
		);

		var containerSession = ControlBuilder.container
		([
			containerFileUnified,
			containerSplitAndJoin,
			containerFilesSplit,
		]);
		
		var divMain = document.getElementById("divMain");
		divMain.innerHTML = "";
		divMain.appendChild(containerSession);
	}
}

function Globals()
{}
{
	// instance

	Globals.Instance = new Globals();

	// methods

	Globals.prototype.initialize = function()
	{
		this.fileSplittingSession = new FileSplittingSession();
		this.fileSplittingSession.domElementUpdate();
	}
}

// run

main();

</script>

</body>
</html>
Advertisements
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s