I have a form that represents an item. The form contains a submit button. If the submit button is clicked, validation unobtrusive validation on these fields should occur.
If the validation fails, nothing else should happen.
If the validation passes, the item should be added to a Knockout.js observedArray collection.
In both cases, the entire process should remain on client side without a submission to the server. Submission and server side validation will take place at a later stage of the process.
How can I achieve the desired effect?
I am using ASP.Net MVC with Data Annotations. I prefer not to manually duplicate validation logic on the client side.
I should also mention that I have several forms on the same page.
Here is what I have done this far...
Here is my ASP.Net MVC layout file:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - JC Guns Online</title>
@*---------- Stylesheets ----------*@
@Styles.Render("~/Content/Bootstrap/bootstrap-theme.css")
@Styles.Render("~/Content/MightyIT/bootstrap_customizations.css")
@Styles.Render("~/Content/site.css")
@Styles.Render("~/Content/MightyIT/custom_styles.css")
@Styles.Render("~/Content/MightyIT/callout.css")
@Styles.Render("~/Content/font-awesome-4.0.3/css/font-awesome.min.css")
@RenderSection("css", required: false)
</head>
<body>
<div class="navbar navbar-default navbar-fixed-top">
<div class="container">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li>@Html.ActionLink("Home", "Index", "Home")</li>
<li>@Html.ActionLink("Contact", "Contact", "Home")</li>
@*<li>
@using (Html.BeginForm())
{
<input id="txtQuickSearch" type="text" class="form-control col-lg-8" placeholder="Search">
<img src="~/Content/img/search_32.png" />
}
</li>*@
</ul>
@Html.Partial("_LoginPartial")
</div>
</div>
<div class="container body-content">
<br />
@RenderBody()
<br /><br />
<nav class="navbar navbar-default navbar-fixed-bottom">
<div style="text-align:center">
<img src="~/Content/img/logo_small.png" class="img-responsive" />
<sub style="position:absolute; right:10px; bottom:10px;">© @DateTime.Now.Year </sub>
</div>
</nav>
</div>
@*---------- Javascripts ----------*@
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/bootstrap")
@Scripts.Render("~/Scripts/KnockOut/knockout-3.0.0.js")
@Scripts.Render("~/Scripts/JQuery/jquery.unobtrusive-ajax.js")
@Scripts.Render("~/Scripts/JQuery/jquery.validate.js")
@Scripts.Render("~/Scripts/JQuery/jquery.validate.unobtrusive.js")
@Scripts.Render("~/Scripts/JQuery/jquery.callout.unobtrusive.js")
@Scripts.Render("~/Scripts/MVCFoolProof/mvcfoolproof.unobtrusive.js")
@RenderSection("scripts",false)
</body>
</html>
Here is the code for the relevant partial that I am currently working on (there are a couple of similar partials that will be placed on the same page):
<form id="AddCrimeForm">
<div class="panel panel-success">
<div class="panel-heading">
<div class="form-horizontal">
<div class="row">
<div class="col-lg-11">Add a crime incident to the list</div>
<div class="col-lg-1">
<button type="submit" class="btn btn-success btn-xs" onclick="addCrime();"><i class="fa fa-plus"></i> Add</button>
</div>
</div>
</div>
</div>
<div class="panel-body">
<div class="form-horizontal">
<div class="row">
<div class="col-lg-6">
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="">
<div class="form-group">
<label class="control-label col-md-4" for="CaseNumber">Case Number</label>
<div class="col-md-8">
<input class="form-control text-box single-line" data-val="true" data-val-required="The Case Number field is required." id="CaseNumber" name="CaseNumber" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="CaseNumber" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-4" for="DateOfIncident">Date Of Incident</label>
<div class="col-md-8">
<input class="form-control text-box single-line valid" data-val="true" data-val-required="The Date of Incident field is required." id="DateOfIncident" name="DateOfIncident" type="date" value="">
<span class="field-validation-valid" data-valmsg-for="DateOfIncident" data-valmsg-replace="true"></span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label class="control-label col-md-4" for="Description">Description</label>
<div class="col-md-8">
<textarea class="form-control text-box multi-line" data-val="true" data-val-required="The Description field is required." id="Description" name="Description"></textarea>
<span class="field-validation-valid" data-valmsg-for="Description" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<table class="table table-striped table-hover " id="CrimeList">
<thead>
<tr>
<th>Case Number</th>
<th>Date of Incident</th>
<th>Description</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: $data.CaseNumber">Column content</td>
<td data-bind="text: $data.DateOfIncident">Column content</td>
<td data-bind="text: $data.Description" style="text-wrap: normal">Column content</td>
@*<td></td>
<td></td>
<td></td>*@
<td>...</td>
</tr>
</tbody>
</table>
And here is the code for client_crime_kjs.js, with all my KnouckoutJS viewmodel code:
$(document).ready(
function ()
{
var Crime = function(CaseNumber, DateOfIncident, Description)
{
this.CaseNumber = CaseNumber;
this.DateOfIncident = DateOfIncident;
this.Description = Description;
}
var initialData = new Array();
var crimes = function (items)
{
var self = this;
//Data
self.items = ko.observableArray(items)
//operations
self.addCrime = function()
{
if ($("#AddCrimeForm").valid()) {
self.crime = new Crime($("#CaseNumber").val(), $("#DateOfIncident").val(), $("#Description").val());
//var JSONObj = { CaseNumber: $("#CaseNumber").val(), DateOfIncident: $("#DateOfIncident").val(), Description: $("#Description").val() };
self.items.push(this.crime);
}
//$("#CaseNumber").val() = "";
//$("#DateOfIncident").val() = "";
//$("#Description").val() = "";
}
}
ko.applyBindings(crimes(initialData), $("#CrimeList")[0])
}
);
Basically what happens is at this stage, is that when the fields are invalid, the form does not submit (rightly so), but when it does validate it does submit (contrary to my requirement), and my KO observablearray subsequently resets.
So I got the answer to the above question. The trick is to set the button type="button" in stead of "submit".
So, for anyone else struggling with this, here is an example of how to get it to work...
Your knockout ViewModel:
$(document).ready(
function () {
var Crime = function (CaseNumber, DateOfIncident, Description) {
this.CaseNumber = CaseNumber;
this.DateOfIncident = DateOfIncident;
this.Description = Description;
}
var crimes = function (items) {
var self = this;
//Data
self.items = ko.observableArray(items)
//operations
self.addCrime = function () {
if ($("#AddCrimeForm").valid()) {
self.crime = new Crime($("#CaseNumber").val(), $("#DateOfIncident").val(), $("#Description").val());
//var JSONObj = { CaseNumber: $("#CaseNumber").val(), DateOfIncident: $("#DateOfIncident").val(), Description: $("#Description").val() };
self.items.push(this.crime);
$("#CaseNumber").val("");
$("#DateOfIncident").val("");
$("#Description").val("");
}
}
self.removeCrime = function (item) {
self.items().remove(item);
}
}
var initialData = new Array();
ko.applyBindings(crimes(initialData), $("#CrimeList")[0])
}
);
And here is the corresponding HTML:
<form id="AddCrimeForm">
<div class="panel panel-success">
<div class="panel-heading">
<div class="form-horizontal">
<div class="row">
<div class="col-lg-11">Add a crime incident to the list</div>
<div class="col-lg-1">
<button type="button" class="btn btn-success btn-xs" onclick="addCrime();"><i class="fa fa-plus"></i> Add</button>
</div>
</div>
</div>
</div>
<div class="panel-body">
<div class="form-horizontal">
<div class="row">
<div class="col-lg-6">
<input data-val="true" data-val-number="The field Id must be a number." data-val-required="The Id field is required." id="Id" name="Id" type="hidden" value="">
<div class="form-group">
<label class="control-label col-md-4" for="CaseNumber">Case Number</label>
<div class="col-md-8">
<input class="form-control text-box single-line" data-val="true" data-val-required="The Case Number field is required." id="CaseNumber" name="CaseNumber" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="CaseNumber" data-valmsg-replace="true"></span>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-4" for="DateOfIncident">Date Of Incident</label>
<div class="col-md-8">
<input class="form-control text-box single-line valid" data-val="true" data-val-required="The Date of Incident field is required." id="DateOfIncident" name="DateOfIncident" type="date" value="">
<span class="field-validation-valid" data-valmsg-for="DateOfIncident" data-valmsg-replace="true"></span>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="form-group">
<label class="control-label col-md-4" for="Description">Description</label>
<div class="col-md-8">
<textarea class="form-control text-box multi-line" data-val="true" data-val-required="The Description field is required." id="Description" name="Description"></textarea>
<span class="field-validation-valid" data-valmsg-for="Description" data-valmsg-replace="true"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
<table class="table table-striped table-hover " id="CrimeList">
<thead>
<tr>
<th>Case Number</th>
<th>Date of Incident</th>
<th>Description</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td data-bind="text: $data.CaseNumber">Column content</td>
<td data-bind="text: $data.DateOfIncident">Column content</td>
<td data-bind="text: $data.Description" style="text-wrap: normal">Column content</td>
@*<td></td>
<td></td>
<td></td>*@
<td>...</td>
</tr>
</tbody>
</table>
Once again - take notice that the "Add" button's type has been set to "button" and NOT "submit".
Hope this helps the rest of all you coding peeps out there!