Continuing from the
previous JavaScript post, I wanted to do some different user interfaces.
At first I wanted to do
a form with the button that allowed the user to navigate and selected the file
to be parsed. And since I found a method
to do a popup I thought I would use a popup to report the results. This post had html that displayed a form
with three label, edit box combinations for the entry of a name, address and
phone number plus a button to be clicked to cause the entries to be displayed
in a popup.
So I wanted to do
something similar by just putting the button of the first solution in the form
and then when the file had been parsed, displaying the results in a popup.
However, since the
reference to the button used in the first solution was somehow hidden – that
is, never directly referenced in the solution with no code for it visible in
the html. As mentioned, this hidden
button allowed the user to navigate Windows folders to find the wanted file and
select it as the text file to be treated.
So the question was "how to select the file to be parsed?"
So I attempted to just
have the user enter the path to the file in an edit box. But the method to open the file from the
first post was using a file passed as the first element in an array structure
where the selection was somehow more than just the path to file. So just supplying the path didn't work to
open the file.
Continuing the searches
I did find another post by Microsoft with the code to select a file via Windows
Explorer and drag and drop it into a drop box.
Capturing the code provided it worked to select a file that way. And in used the files in an array where each
element of the array was part of a structure; that is,
f = files[i]; // If anything goes
awry, the error would occur here.
output.push('<li><strong>',
f.name,
'</strong> (',
f.type || 'unknown
file type',
') - ',
f.size, ' bytes, last
modified: ',
f.lastModifiedDate,
'</li>');
document.getElementById('list').innerHTML = output.join('');
to display data about
the selected file. So I figured that
perhaps the filePath.files[0] of the first post that was used to open the file
would be of the same structure as the files[i] of the Microsoft post.
Therefore, I set about
adding to the Microsoft drag and drop box to open the selected file and having
the learntocode.js code of my previous post parse the file and determine the
file contents key and value with the maximum number of references.
Before lunch it was
working and was time to add the popup to display the results.
However the popup
refused to pop up. Even thou the
internet samples did. Then found out
that the window.open was returning null when used in my code.
It took quite a while
to get a working application. I would
make a change and the html wouldn't work again and I would have to start over
since I couldn't detect what part of my change caused it to stop working. Therefore, I began to retain working samples
and make a copy from which to continue so I could go back to the working sample
when changes to the copy ceased to work.
Eventually, via alerts,
I found some strange behaviour. The
display() function that did a window.open of the popup was invoked following
the return from the parse() function of the learntocode.js file. The parse() function was getting called
twice and hence the display() function.
The first time parse was called the buffer from reading the selected
file had a 0 length so parse didn't have anything to work with and would return
results of 0,0,0. It would pass this to
display. Then somehow parse would get
invoked again with the actual data (as if there was a hidden callback in the
display function). Then, upon return
from parse, display would get entered again (although the alert at its
beginning wouldn't occur) but now display would have the actual parse results
and they would be displayed twice.
Therefore, I added code
to only display the results when entered for the second time.
I don't understand this
behaviour. Especially since JavaScript
is supposed to be single threaded from what I find on the internet. I had thought that the window read of the
file was being done in another thread so that the regular thread was continuing
while the read thread was taking place.
Thus resulting in a buffer length of 0 in the html thread. Then when the assumed window read thread
finished, the buffer contained the data.
Even if so, I don't understand how the parse function was re-entered
with the now filled buffer.
But, incrementing the
counter in display() and only calling DispWin.document.write (where DispWin is
the result of the window.open) upon the second entry resulted in the expected
results.
A second problem is
that an attempt to display a title for the popup failed.
The browser window, the same one after the file dropped into the drag and drop box, and the popup with the results follow.
This new solution with
a drag and drop box for supplying the file to be parsed and the popup to
display the results is as follows.
LearntoCode-popup.html
<!DOCTYPE
html>
<html>
<head>
<title>Drag & Learn To Code
- popup</title>
<meta
http-equiv="X-UA-Compatible" content="IE=10">
<style>
#fileDropBox {
width: 20em;
line-height: 10em;
border: 1px dashed gray;
text-align: center;
color: gray;
border-radius: 7px;
</style>
<script
type="text/javascript"
src="learntocode.js"></script>
<script LANGUAGE="JavaScript"
type="text/javascript">
function display(results) {
var DispWin = window.open('','NewWin',
'toolbar=no,status=no,width=300,height=200');
message =
"<ul><b>Results from Parse of File</b> \n";
message +=
"<li><b>NAME: </b>" + results[0];
message += "<li><b>KEY:
</b>" + results[1];
message +=
"<li><b>INSTANCES: </b>" + results[2] +
"</ul>";
/* NOTE: For some reason display is
entered a second time although after the window.open
so without the test below the
results are displayed twice. Also, the
call to
parse is done twice that is
followed by the call to this function.
The first
time parse is called, the data
buffer is empty whereas the second time it is
full. So the first time this function is entered the results passed in
are
0,0,0 while the second time
they are the actual results. */
if (displayEntryCount==1)
{
DispWin.document.title = "Learn
to Code Popup"; // this doesn't work
DispWin.document.write(message);
} // end if
displayEntryCount++;
} // end display
var reader; /*GLOBAL File Reader object*/
var displayEntryCount = 0; // count of the
number of times display is entered
function checkFileAPI() {
if (window.File &&
window.FileReader && window.FileList && window.Blob) {
reader = new FileReader();
return true;
} else {
alert('The File APIs are not fully
supported by your browser. Fallback required.');
return false;
}
} // end checkFileAPI
function readText(files) {
var result = checkFileAPI(); // create
the FileReader
var buffer = ""; /* to contain
the file text */
if (files && files[0]) {
reader.onload = function (e) {
buffer = e.target.result;
treatFile(buffer);
} // end reader/onload function
reader.readAsText(files[0]);
treatFile(buffer);
} // end if html5 filelist support
else if (ActiveXObject &&
files[0]) { // fallback to IE 6-8 support via ActiveX
try {
//<<< won't work since need filePath that no longer have
>>>
reader = new
ActiveXObject("Scripting.FileSystemObject");
var file =
reader.OpenTextFile(files, 1); // ActiveX File Object
buffer = file.ReadAll(); // text
contents of file
file.Close(); // close file
"input stream"
treatFile(buffer);
} catch (e) {
if (e.number == -2146827859) {
alert('Unable to access local files
due to browser security ' +
'settings. ' + 'To overcome
this, go to ' +
'Tools->Internet
Options->Security->Custom Level. ' +
'Find the setting for
"Initialize and script ActiveX ' +
'controls not marked as safe" and change it ' +
'to "Enable" or
"Prompt"');
}
}
}
else { // this is where you could
fallback to Java Applet, Flash or similar
return false;
}
return true;
} // end readText
function treatFile(buffer)
{
var results = [];
results = parse(buffer);
display(results);
} // end treatFile
</script>
</head>
<body>
<h1>Drag and Drop File
Selection</h1>
<h3>Learn to Code Example
2</h3>
<p>Using Windows Explorer (or
similar), select one or more files (directories are
not allowed), and then drag them to the
below drop box:</p>
<div id="fileDropBox">Drop
files here.</div>
<ul id="list"></ul>
<script>
var message = [];
if (!window.FileReader) {
message = '<p>The ' +
'<a
href="http://dev.w3.org/2006/webapi/FileAPI/"
target="_blank">File API</a>s ' +
'are not fully supported by
this browser.</p>' +
'<p>Upgrade your
browser to the latest version.</p>';
document.querySelector('body').innerHTML = message;
}
else {
// Set up the file drag and drop
listeners:
document.getElementById('fileDropBox').addEventListener('dragover',
handleDragOver, false);
document.getElementById('fileDropBox').addEventListener('drop',
handleFileSelection, false);
}
function handleDragOver(evt) {
evt.stopPropagation(); // Do not allow the dragover event to
bubble.
evt.preventDefault(); // Prevent
default dragover event behavior.
} // handleDragOver()
function handleFileSelection(evt) {
evt.stopPropagation(); // Do not allow
the drop event to bubble.
evt.preventDefault(); // Prevent
default drop event behavior.
var files = evt.dataTransfer.files; //
Grab the list of files dragged to the drop box.
if (!files) {
msa.alert("<p>At least
one selected file is invalid - do not select any
folders.</p><p>Please reselect and try again.</p>");
return;
}
// "files" is a FileList of
file objects. List a few file object properties:
var output = [];
for (var i = 0, f; i <
files.length; i++) {
try {
f = files[i]; // If anything goes
awry, the error would occur here.
output.push('<li><strong>',
f.name,
'</strong> (',
f.type || 'unknown
file type',
') - ',
f.size, ' bytes, last
modified: ',
f.lastModifiedDate,
'</li>');
document.getElementById('list').innerHTML = output.join('');
} // try
catch (fileError) {
msa.alert("<p>An
unspecified file error occurred.</p><p>Selecting one or more
folders will cause a file error.</p>");
console.log("The following
error occurred at i = " + i + ": " + fileError); // Send the
error object to the browser's debugger console window, if active.
return;
} // catch
} // for
readText(files);
} // handleFileSelection()
</script>
<script src="../utilities.js"
type="text/javascript"></script> <!-- Provides the
msa.alert() method. -->
</body>
</html>
The learntocode.js file
is unchanged from the initial solution.
learntocode.js
// Global data
// The following
would be the KeyData and KeyTable structures if there were
// something like
struct or class in JavaScript.
var keyCount = 0;
var valueKey = [];
var valueCount = [];
var valueValues =
[,]; // different values of the key
var valueSums =
[,]; // sum of values of particular
key
// Data to be
retained as the key and its associated value with the maximum
// number of
references.
var maxKey = 0;
var maxValue = 0;
var maxSum = 0;
// Saved data as
parse each line.
var savedData = [];
savedData[0] = 0;
savedData[1] = 0;
savedData[2] = 0;
// Functions
// Parse data lines
of the text file.
function parse(data)
{
var CR = "\r";
var HT = "\t";
var LF = "\n";
/* Parse each line of data in the buffer
to obtain the three fields of
* interest, converting those fields to integers
into an array, and
* then updating a data structure to
retain the data for evaluation
* when the complete buffer has been
parsed.
*/
var nextField = 0; // index of the beginning of next field
var startField; // range of indexes of
var numFields = 0; // index into dataFields array
var dataFields = []; // Integer values of
the three fields of interest
dataFields[0] = 0;
dataFields[1] = 0;
dataFields[2] = 0;
var bufSize = data.length;
var index = 0;
for (index = 0; index < bufSize;
index++ )
{
// Parse the buffer line
if (data[index] == HT) // beginning of
a field
{
startField = nextField; // save starting index
nextField = index + 1; // the next byte will contain part of next
field
if (numFields > 0)
{ //convert and store in
dataFields
dataFields[numFields-1] =
toInt(data, startField, index - 1);
} // end if numFields > 0
numFields++;
} // end if HT
// Do the update when find CR or LF of
reach the end of the buffer.
// The last option since the file has
no terminators for the last line.
if ((data[index] == CR) ||
(data[index] == LF) || (index == (bufSize-1)))
{
if (numFields < 4) // only do
the update once for each line
{ //convert and store in
dataFields
dataFields[numFields-1] =
toInt(data, nextField, index - 1);
// Save the data to determine
the key, value combination with
// maximum number of
references
update(dataFields);
// Finished with the line in
the data array, initialize
// for next line.
nextField = 0;
startField;
numFields = 0;
dataFields[0] = 0;
dataFields[1] = 0;
dataFields[2] = 0;
if ((data[index] == CR)
&& (data[index+1] == LF))
{ index++ } // bypass the
extra char at end of line
} // end if
} // end if
} // end for loop
// Report the number of references with a
particular Key and Value.
var results = [];
results = report();
return results;
}; // end parse
// Convert character
array to integer
function toInt(data,
iS, iE)
{
var NINE = "9" //57 // ASCII
character for digit 9
var ZERO = "0" //48 // ASCII
character for digit 0
var index = iE; // loop in reverse
var digit = 0;
var m = 1; // multiplier for shift
var number = 0; // Numeric result
while (index >= iS)
{
if ((data[index] >= ZERO)
&& (data[index] <= NINE))
{
digit = data[index] - ZERO; //
convert ASCII to digit
number = number + (m * digit);
m = m * 10;
index--;
}
}
return number;
} // end toInt
// Update the tables for a particular file line.
function update(data)
{
// This function
first checks if the key is new and, if so, adds it to the
// keys array. It then does similar for the values
associated with it.
// Note: data[0] is
the key while data[1] and data[2] are the two values
// associated with the key.
// Save data to be
updated for use by updateTotals since needed by
// multiple functions
savedData[0] =
data[0];
savedData[1] =
data[1];
savedData[2] =
data[2];
// Check whether the
key is already in the key table
var keyIndex = -1;
for (var k = 0; k
< keyCount; k++)
{
if (valueKey[k]
== data[0])
{
keyIndex = k; // key
already in the table
break; //
exit loop
}
} // end for
if (keyIndex < 0)
// key not in the table
{ // add the key
keyIndex =
keyCount;
valueKey[keyIndex] = data[0];
valueCount[keyIndex] = 0;
keyCount++;
} // end if
// Add the values for
the key
addValues(keyIndex,
savedData[1], savedData[2]);
} // end update
// Add the values or increment their sums
function addValues(keyIndex, value1, value2)
{
// Check whether the
first value is already in the table
var valueIndex = -1;
for (var v = 0; v
< valueCount[keyIndex]; v++)
{
if
(valueValues[keyIndex,v] == value1)
{ // value already in the table
valueIndex =
v;
valueSums[keyIndex,v]++; // increment its number of references
if (value1 ==
value2) // 2nd value the same
{
valueSums[keyIndex,v]++; // increment again
}
// check if
new max
updateTotals(savedData[0],value1,valueSums[keyIndex,v]);
break; //
exit loop
}
} // end loop
if (valueIndex <
0) // value not yet in table
{ // add value to the
table - index points to last value checked
var index =
valueCount[keyIndex];
valueValues[keyIndex,index] = value1;
valueSums[keyIndex,index] = 1;
valueCount[keyIndex]++;
if (value1 ==
value2) // dupicated value
{
valueSums[keyIndex,index]++; // increment to 2
// check if
new max
updateTotals(savedData[0],value1,valueSums[keyIndex,index]);
}
else
{
add2ndValue(keyIndex, value2);
}
} // end outer if
} // end addValues
function add2ndValue(keyIndex, value2)
{
// Check whether the
second value is already in the table
var valueIndex = -1;
for (var v = 0; v
< valueCount[keyIndex]; v++)
{
if (valueValues[keyIndex,v]
== value2)
{ // value
already in the table
valueIndex =
v;
valueSums[keyIndex,valueIndex]++; // increment number of references
// check if
new max
updateTotals(savedData[0],value2,valueSums[keyIndex,valueIndex]);
break; //
exit loop
}
} // end loop
if (valueIndex <
0) // value not yet in table
{ // add value to the
table
var index =
valueCount[keyIndex];
valueValues[keyIndex,index] = value2;
valueSums[keyIndex,index] = 1;
valueCount[keyIndex]++;
updateTotals(savedData[0],value2,valueSums[keyIndex,index]);
}
} // end add2ndValue
function updateTotals(key, value, sum)
{
if (sum > maxSum)
{
maxKey = key;
maxValue = value;
maxSum = sum;
}
} // end updateTotals
// Report the key and value with the most references
function report()
{
var results = [];
results[0] = maxKey;
results[1] =
maxValue;
results[2] = maxSum;
return results;
} // end report