I'm looking for best approach suggestions for the below scenario:
- A user can choose one or more csv files to validate(Attachment 1) which on "validate" button click goes through the validation code(showing progress bar till it returns an output).
- A return response is either a success message or the error details for each file chosen to validate(Attachment 2)
- A successfully validated file can now be uploaded to azure storage with an "Upload" button.
Attachment 1
Attachment 2
Now, to make everything asynchronous, my thought is that the view needs to have individual flexible section for each file.
I'm working with MVC5 razor views with knockout.js, I have decent idea about partial views but I am not certain on how to go about this. If not partial views, then what would be the best approach.
my thought is that the view needs to have individual flexible section
for each file
Kind of, what I think you need is a separate model/class for the files in order to run ajax commands on demand, at least that's how I understood your explanation.
Check out this jsfiddle, I have added some random true/false and string stuff to try and mimic your layout as quickly as possible. For testing try to use 5 or more files (random generators are kind of finicky in JS).
https://jsfiddle.net/n2ne6yLh/10/
So essentially you listen for a change event on the file input. Map each file to a new model "FileModel" in this case, then push it into the observableArray Files. Each FileModel houses it's own individual results, validation functions etc. Then the layout takes care of the rest.
You will have need to look into the FormData Web API in order to do stuff with the files in Javascript. If your clients/users are using outdated browsers there a shims/polyfills for the FormData stuff, jquery and what have you.
https://developer.mozilla.org/en-US/docs/Web/API/FormData
var PageModel = function(r) {
var self = this;
this.Files = ko.observableArray();
this.FileErrors = ko.computed(function() {
return _.some(self.Files(), function(file) {
return file.IsValid() === false;
});
});
this.ClearFiles = function() {
document.getElementById("your-files").value = "";
self.Files([]);
};
var control = document.getElementById("your-files");
control.addEventListener("change", function(event) {
// When the control has changed, there are new files
var i = 0,
files = control.files,
len = files.length;
var form = new FormData();
for (; i < len; i++) {
form.append(files[i].name, files[i]);
self.Files.push(new FileModel(files[i], files[i]));
}
}, false);
}
var FileModel = function(r, fileObject) {
var self = this;
this.FileObject = fileObject;
this.Name = r.name;
this.Type = r.type;
this.Size = r.size;
this.IsValidated = ko.observable(false);
this.IsValid = ko.observable();
this.ValidationErrors = ko.observable();
this.ValidateFile = function() {
//Do some ajax to validate file
//console.log('Doing an ajax thing.')
// Randomizers for validation, remove in production
var random_boolean = Math.random() >= 0.5;
var random_strins = Math.random().toString(36).substring(7);
// Set vals based on returned ajax response.
self.IsValidated(true);
self.IsValid(random_boolean);
self.ValidationErrors(random_strins);
};
this.UploadFile = function() {
alert('uploading this file to the interwebs, yay!')
}
}
window.model = new PageModel();
ko.applyBindings(model);
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-push-3">
<div class="form-group">
<div class="input-group">
<input type="file" class="form-control" id="your-files" multiple>
<span class="input-group-btn">
<button class="btn btn-info" data-bind="click: ClearFiles">Clear</button>
</span>
</div>
</div>
</div>
</div>
</div>
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<h4>Validate Files</h4>
<!-- ko if: Files().length > 0 -->
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Size (bytes)</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: Files -->
<tr data-bind="css: IsValid() ? 'success' : ''">
<td><span data-bind="text: Name"></span>
</td>
<td><span data-bind="text: Type"></span>
</td>
<td><span data-bind="text: Size"></span>
</td>
<td>
<button class="btn btn-sm btn-success" data-bind="click: ValidateFile, visible: !IsValidated()">Validate</button>
<button class="btn btn-sm btn-success" data-bind="click: UploadFile, visible: IsValid()">Upload</button>
</td>
</tr>
<!-- /ko -->
</tbody>
</table>
<!-- /ko -->
</div>
<div class="col-sm-6">
<h4>File Errors</h4>
<!-- ko if: FileErrors() -->
<table class="table table-hovered">
<thead>
<tr>
<th>Name</th>
<th>Error Message</th>
</tr>
</thead>
<tbody>
<!-- ko foreach: Files -->
<!-- ko if: IsValid() == false -->
<tr>
<td data-bind="text: Name"></td>
<td data-bind="text: ValidationErrors"></td>
</tr>
<!-- /ko -->
<!-- /ko -->
</tbody>
</table>
<!-- /ko -->
</div>
</div>
</div>
What about the following simple algorithm
Let's assume in your html you have a < div id="allFiles">
You can write the following in your main view file
function validateFiles(filesToValidate)) {
foreach(file in filesToValidate)
{
var fileDivWithIdTheNameOfTheFile = @Html.RenderPartial("A_View_WithProgressBar",file)
allFiles.AddElement(fileDivWithIdTheNameOfTheFile );
ajax.Get("YourActionThatReturnsAResultView",file)
.OnSuccess(args)
{
FindDivForCurrentFile.ReplaceWith(args.ResultView)
}
}
}
This way most of you code is on the ServerSide,all you need is some jquery code to replace parts of the page once a validation of a file has been completed
Going with a partial view will work. Just have one partial view with the basic table layout for what you need. Then have
<div id='partial'/>
in your main form that is hidden. Post your file using knockout or jquery to your upload action. Let the action return your partial view. Then using knockout or jquery's success callback, do something like this:
success: function(data) {
$('#partial').html(data);
}
to insert your partial view's html