Introduction
The screenshot below shows the finished project; with red lines, the major issues that need fixing. I also included some regex code to run a basic validation of email addresses.

This is the example CSV file. Note, that not all the data we require is there, and that is the problem.
Background
The objective of the project is to allow a user to upload a CSV file, parse its client-side (before going to the server), and show the user what data needs to be corrected before they can upload the file for proper integration.
The workflow is as follows:
- User selects a CSV file (sample provided).
- User clicks a button (weehoo...).
- Client-side code takes the CSV, parses it, and loads it into KnockoutJS array.
- KnockoutJS observables do their magic of displaying green/go, red/stop to the user.
To keep things clean, I left the JavaScript and CSS in their own separate files. The main three files we will be working with are the HTML (index.cshtml), JavaScript (ViewScript.js), and CSS (KnockoutStyles.css).
The first thing to set-up in KO is the model. In this case, I am capturing basic user information. You will note, I have a field for password - in the production version. I didn't bring in the plain text password, but used a hash (the sample data is different, however, for illustration purposes).
- function userModel() {
- this.UserName = ko.observable();
- this.Password = ko.observable();
- this.FirstName = ko.observable();
- this.LastName = ko.observable();
- this.Email = ko.observable();
- // Define ViewModel which is an array of user details model
- var userVM = {
- userModel: ko.observableArray([])
- }
- };
- // Bind the VM to the UI
- ko.applyBindings(userVM);
- <table border="1">
- <thead>
- <tr>
- <th class="Pad">Username</th>
- <th class="Pad">Password</th>
- <th class="Pad">First name</th>
- <th class="Pad">Last name</th>
- <th class="Pad">Email address</th>
- <th class="Pad"> </th>
- </tr>
- </thead>
- <tbody data-bind="foreach: userModel">
- <tr data-bind="css: ValidCredentials">
- <td class="Pad"><input data-bind="value: UserName" class="Standard"/></td>
- <td class="Pad"><input data-bind="value: Password" class="Standard" /></td>
- <td class="Pad"><input data-bind="value: FirstName" class="Standard"/></td>
- <td class="Pad"><input data-bind="value: LastName" class="Standard"/></td>
- <td class="Pad"><input data-bind="value: Email" class="Wide"/> <br /></td>
- <td class="Pad"></td>
- </tr>
- </tbody>
- </table>
- <form>
- <input type="file" id="UserFile" />
- <a href="#" id="lnkUpload">Upload CSV</a>
- </form>
- $('#lnkUpload').click(function () {
- var FileToRead = document.getElementById('UserFile');
- if (FileToRead.files.length > 0) {
- var reader = new FileReader();
- // assign function to the OnLoad event of the FileReader
- // non synchronous event, therefore assign to other method
- reader.onload = Load_CSVData;
- // call the reader to capture the file
- reader.readAsText(FileToRead.files.item(0));
- }
- });
There, as one gotcha that had me head scratching for a while, I tried to use a jQuery selector to get the varFileToRead - this didn't work, however; so, I fell back to raw JavaScript and everything flowed smoothly again.
The FileReader "OnLoad" event that reads the file contents is not synchronous. So, I assigned it to another function (Load_CSVData) and then called the readAsText event which feeds back into that function.
Now, something to note here. The FileReader will only work if you are running from a WebServer (or within the IDE using the integrated web server etc.) - it does *not* work if you are simply running form txt files on the desktop.
The logic of the Load_CSVData method is, as follows:
- Clear any items from the existing KO observable array.
- Load up a local array (CSVLines) with the text data passed in from the FileReader, separating out each line using the "Split" function (delimiter = new line marker).
- For each line being loaded, split this further (delimiter = the comma).
- For each item in each CSV line, push the value to as a new model on the VM array.
I broke the code below, out a bit, to make it easier to read. Note that if there is no value in the CSV line, I added a blank string to save problems later.
- function Load_CSVData(e) {
- userVM.userModel.removeAll();
- CSVLines = e.target.result.split(/\r\n|\n/);
- $.each(CSVLines, function (i, item) {
- var element = item.split(","); // builds an array from comma delimited items
- var LUserName = (element[0] == undefined) ? "" : element[0].trim();
- var LPassword = (element[1] == undefined) ? "" : element[1].trim();
- var LFirstName = (element[2] == undefined) ? "" : element[2].trim();
- var LLastName = (element[3] == undefined) ? "" : element[3].trim();
- var LEmailAddress = (element[4] == undefined) ? "" : element[4].trim();
- userVM.userModel.push(new userModel()
- .UserName(LUserName)
- .Password(LPassword)
- .FirstName(LFirstName)
- .LastName(LLastName)
- .Email(LEmailAddress)
- )
- });
- }

The next step is to use the power of the observable pattern to adjust the UI as data is received and changed...
There are a number of improvements to be made:
- Highlight rows that have a problem in red.
- Make it obvious when data is required (we will give the input a yellow color).
- Show rows that are OK or almost OK, in green.
- Show a message if the email address provided is not valid.
On each table row, I added in a CSS data-bind that called a computed function. This function checked if the required fields (Username, Password, Email) had values and if not, set the CSS background color for the row to Red.
- .Red {
- border: thin dotted #FF6600;
- background-color: red;
- }
- <tr data-bind="css: ValidCredentials">
- <td class="Pad"><input...
- this.ValidCredentials = ko.computed(function () {
- var ValidCreds = (this.UserName() != "" &&
- this.Password() != "" && this.Email() != "");
- return !ValidCreds ? "Red" : "Green";
- }, this);
- <td class="Pad"><input data-bind="value: UserName, css: USR_Required" class="Standard"/></td>
- <td class="Pad"><input data-bind="value: Password, css: PWD_Required" class="Standard" /></td>
- <td class="Pad"><input data-bind="value: FirstName" class="Standard"/></td>
- <td class="Pad"><input data-bind="value: LastName" class="Standard"/></td>
- <td class="Pad"><input data-bind="value: Email, css: EML_Required" class="Wide"/>
- this.USR_Required = ko.computed(function () {
- var rslt = (this.UserName() != "")
- return rslt == true ? "NR" : "Required";
- }, this);
- this.PWD_Required = ko.computed(function () {
- var rslt = (this.Password() != "")
- return rslt == true ? "NR" : "Required";
- }, this);
- this.EML_Required = ko.computed(function () {
- var rslt = (this.Email() != "")
- return rslt == true ? "NR" : "Required";
- }, this);
- .Required {
- background-color: #FFFF00;
- }
- .NR {
- background-color: #FFFFFF;
- }
- this.InValidEmail = ko.computed(function () {
- if (this.Email() == "") // dont test if no value present
- {
- return false;
- }
- var rslt = !validateEmail(this.Email());
- return rslt;
- }, this);
- function validateEmail(email) {
- var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)
- *)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.
- [0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
- return re.test(email);
- }
Almost there. The final thing was to give the user a means of removing a complete line of data if it was not relevant (think about all of those accounts in Active Directory of people who have left an organization over the years....).
To do this, I added a "Click" data-bind to the end of each row that called a removeUser method and added code to the ViewModel to drop the item from the KO array.
- <a href="#" data-bind="click: $parent.removeUser"><span class="White">Remove user</span></a>
- function RemoveUserFromList(data) {
- userVM.userModel.remove(data);
- }
- var userVM = {
- userModel: ko.observableArray([]),
- removeUser: function (data) {
- RemoveUserFromList(data)
- }
- };
That's it.

Ravi KandelPosted Aug 14, 2016, 3:10 AM
Nice
Bhavik PatelPosted Aug 2, 2016, 10:54 PM
Nice
Bikesh SrivastavaPosted Jul 31, 2016, 10:15 PM
Nice article
Prasanna MuraliPosted Jul 27, 2016, 9:41 PM
Nice share
Vignesh ManiPosted Jul 27, 2016, 4:55 AM
Good one
Asad AliPosted Jul 27, 2016, 3:34 AM
A good article
kalu singh raoPosted Jul 26, 2016, 1:27 AM
Nice sharing