I have created a form that has multiple sections that are hidden. A tab strip hides/shows the sections to create a page with a smaller footprint. While this makes the page a lot cleaner, it makes it hard to show errors to the user after validation. I want to make an indicator in the tabs that shows that the content in the specified tab has errors.
Main View:
<div>
<ul class="contentTabs">
<li onclick="switchTab(this)" class="selected">Contact</li>
<li onclick="switchTab(this)">Information</li>
<li onclick="switchTab(this)">Software</li>
<li onclick="switchTab(this)">Hardware</li>
<li onclick="switchTab(this)">Classification</li>
<li onclick="switchTab(this)" class="last">Solution</li>
</ul>
<div class="content">
<div id="contact" class="contentPane">
#Html.Partial("_Contact")
</div>
<div id="information" class="contentPane" style="display: none;">
#Html.Partial("_Information")
#Html.Partial("_Notes")
</div>
<div id="notes" class="contentPane" style="display: none;">
#Html.Partial("_Notes")
</div>
<div id="software" class="contentPane" style="display: none;">
#Html.Partial("_Software")
</div>
<div id="hardware" class="contentPane" style="display: none;">
#Html.Partial("_Hardware")
</div>
<div id="classification" class="contentPane" style="display: none;">
#Html.Partial("_Classification")
</div>
<div id="solution" class="contentPane" style="display: none;">
#Html.Partial("_Solution")
</div>
</div>
</div>
Partial View (Contact):
#code
Dim notifyTypes As ListItemCollection = DirectCast(ViewData("NotifyTypes"), ListItemCollection)
Dim callerTypes As ListItemCollection = DirectCast(ViewData("CallerTypes"), ListItemCollection)
Dim reportingTypes As ListItemCollection = DirectCast(ViewData("ReportingTypes"), ListItemCollection)
Dim myIncident As Library.BusinessLayer.Incident = DirectCast(Model, Library.BusinessLayer.Incident)
End Code
<table class="tableBorderless" style="width: 99%; margin: 0px auto">
<tr>
<td class="right">User Location</td>
<td class="left">
#Html.DropDownList("LocationId", DirectCast(ViewData("Locations"), SelectList), New With {.style = "width: 200px"})<br />
#Html.ValidationMessage("LocationId", New With {.class = "red"})
</td>
<td class="right">Notify</td>
<td class="left">
#For Each notificationType As ListItem In notifyTypes
#<input type="radio" name="Notify" value="#notificationType.Value" #IIf(notificationType.Selected, "checked", "") />#notificationType.Text
Next
</td>
</tr>
<tr>
<td class="right">Caller Type</td>
<td colspan="3" class="left">
#For Each callerType As ListItem In callerTypes
#<input type="radio" name="CallerType" value="#callerType.Value" #IIf(callerType.Selected, "checked", "") />#callerType.Text
Next
</td>
</tr>
<tr>
<td class="right">User Network ID</td>
<td class="left">
#Html.TextBox("UserId", myIncident.UserId, New With {.onchange = "UserId_onchange(this)", .maxlength = "30"})
</td>
<td class="right">User Name</td>
<td class="left">
#Html.TextBox("UserName", myIncident.UserName, New With {.maxlength = "50"})<br />
#Html.ValidationMessage("UserName", New With{.class = "red"})
</td>
</tr>
<tr>
<td class="right">User Email</td>
<td class="left">
#Html.TextBox("UserEmail", myIncident.UserEmail, New With {.maxlength = "50"})<br />
#Html.ValidationMessage("UserEmail", New With{.class = "red"})
</td>
<td class="right">User Phone</td>
<td class="left">
#Html.TextBox("UserPhone", myIncident.UserPhone, New With {.maxlength = "50"})
</td>
</tr>
<tr>
<td class="right">Reporting Type</td>
<td colspan="3" class="left">
#For Each reportingType As ListItem In ReportingTypes
#<input type="radio" name="ReportedByType" value="#reportingType.Value" #IIf(reportingType.Selected, "checked", "") />#reportingType.Text
Next
</td>
</tr>
<tr>
<td class="right">Reported by (Network ID)</td>
<td class="left">
#Html.TextBox("ReportedByUserId", myIncident.ReportedByUserId, New With {.onchange = "ReportedByUserId_onchange(this)", .maxlength = "30"})
</td>
<td class="right">Reported by Name</td>
<td class="left">
#Html.TextBox("ReportedByName", myIncident.ReportedByName, New With {.maxlength = "50"})<br />
#Html.ValidationMessage("ReportedByName", New With {.class = "red"})
</td>
</tr>
<tr>
<td class="right">Reported by Email</td>
<td class="left">
#Html.TextBox("ReportedByEmail", myIncident.ReportedByEmail, New With {.maxlength = "50"})<br />
#Html.ValidationMessage("ReportedByEmail", New With {.class = "red"})
</td>
<td class="right">Reported by Phone</td>
<td class="left">
#Html.TextBox("ReportedByPhone", myIncident.ReportedByPhone, New With {.maxlength = "50"})
</td>
</tr>
</table>
<script type="text/javascript">
function UserId_onchange(textField) {
var parms = {UserName: textField.value};
$.ajax({
url: '#Url.RouteUrl(New With{.Controller = "Users", .Action = "Get"})',
type: 'POST',
dataType: 'json',
data: parms,
success: function (data) {
$("#UserName").val(data.Name);
$("#UserEmail").val(data.Email);
$("#UserPhone").val(data.PhoneWork);
}
});
}
function ReportedByUserId_onchange(textField) {
var parms = { UserName: textField.value };
$.ajax({
url: '#Url.RouteUrl(New With{.Controller = "Users", .Action = "Get"})',
type: 'POST',
dataType: 'json',
data: parms,
success: function (data) {
$("#ReportedByName").val(data.Name);
$("#ReportedByEmail").val(data.Email);
$("#ReportedByPhone").val(data.PhoneWork);
}
});
}
</script>
You could check whether appropriate tab's div has any "input-validation-error" class applied (taken you use standard DataAnnotations). Combine this into jQuery function which would run through all needed divs (probably all divs specified in your li elements) and if length of elements with "input-validation-error" class is more than 0, as #rivarolle suggested apply "error" class to li element to highlight it in your preferred way.
This would be a possible script:
$( "li" ).each(function( index ) {
var searchPattern = ("#"+$(this).text()+" .input-validation-error");
if ($(searchPattern.toLowerCase()).length > 0){
$(this).addClass("error");
}
});
css:
.error {
background-color: red;
}
Give your li elements IDs
<li onclick="switchTab(this)" id="softwareTab">Software</li>
Then pass the collection of validation objects, or, better a list of affected tab names in your ViewModel, and store the list in one or more hidden fields. Then use jQuery to parse the list and add the error class as suggested by rivarolle...
$("#softwareTab").addClass("error")
You may have to clean up later with removeClass().
There are many ways to do this, all a bit kludgy, but sometimes that is the price of a good looking page...one hidden field with a comma seperated list, one hidden field per tab with a boolean value...or pseudo-boolean.
I think, The page should be divided into partial views. Each partial view needs to be validated before proceeding to next step. For that we can write a helper Method. When user fills the data and post the section, then controller checks and fills your custom validation error collection and it can be passed on as model metadata i.e. buddy class in your model. This way , you will render the errors. i.e. we are using model-metadata to send validation errors.
If you don't want to use model approach then you need to use ViewBag collection which is dynamic collection.
Hope this helps.
What you will probably need to do is use the visibility of the various validation messages.
The way I'd approach this is by adding a custom class to the validation messages for use within jquery:
#Html.ValidationMessage("UserName", New With{.class = "red validationMesssage"})
Then in the switchTab function do something like this:
function switchTab(el)
{
var tabId=$(el).text(); //Get the tab to be searched
var isValid=true; //Set default as valid
$("#"+tabId).find(".validationMessage:visible").each(function(){
isValid=false; //this should only fire if the validation message is visible
});
if(!isValid)
$(el).addClass("errors"); //If invalid..add error class to li element.
}
You can try #Html.ValidationSummary(false) at the top MAIN view. Its better from usability perspective as well.
You could change the color of the tab headers that contain errors to red for instance.
To do this, I would switch the css of the tags.
To take the Information tab:
No error:
--> <li onclick="switchTab(this)">Information</li>
Error:
--> <li onclick="switchTab(this)" class="error">Information</li>
The CSS class "error" will change the color to red or append an image to indicate validation failure.
Related
I am new to ASP.NET Core development. I am looking for something like a built-in way to use loop iteration numbers inside the view of ASP.NET Core.
I did some research and found solutions like creating int variable outside the loop and then increment inside the loop.
I want to index each user.
Here is my code:
#foreach (var item in l_IEnumerableModUserQuery)
{
<tr>
<td>
<!-- Here I want to add Iteration No. here-->
</td>
<td>
<a href="#">
#item.Pr_FullName
</a>
</td>
<td>#item.Pr_Email</td>
<td>#item.Pr_ContactNo</td>
</tr>
}
You could use a simple for loop to get the index:
//use .Count if it is a List or .Count() with Linq to get the boundary.
#for(var i = 0; i < l_IEnumerableModUserQuery.Count; i++)
{
<tr>
<td>
#i.ToString();
</td>
<td>
<a href="#">
#l_IEnumerableModUserQuery[i].Pr_FullName
</a>
</td>
<td>#l_IEnumerableModUserQuery[i].Pr_Email</td>
<td>#l_IEnumerableModUserQuery[i].Pr_ContactNo</td>
</tr>
}
Thomas Levesque has a neat approach on his blog, using an extension method:
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> source)
{
return source.Select((item, index) => (item, index));
}
Which would result in:
#foreach (var (item, idx) in l_IEnumerableModUserQuery.WithIndex())
{
<tr>
<td>
#idx
</td>
<td>
<a href="#">
#item.Pr_FullName
</a>
</td>
<td>#item.Pr_Email</td>
<td>#item.Pr_ContactNo</td>
</tr>
}
With an eye on the extension methods approach, you could as well amend your views model and include the index as a property in your model inside your controller / handler or whereever your model is created:
var l_IEnumerableModUserQuery =
someSource.Where(x => ...)
.Select((x, index) => new MyModel {
Index = index,
Pr_Email = xxx,
Pr_Contact = xxy,
/* ... rest of model */
});
return l_IEnumerableModUserQuery;
After this you could access the index like any other property in your view:
<a href="#">
#item.Index
</a>
you can findout the index of the item
#{
int indx=0;}
#foreach (var item in l_IEnumerableModUserQuery)
{
<tr>
<td>
#l_IEnumerableModUserQuery.IndexOf(item)
</td>
<td>
<a href="#">
#item.Pr_FullName
</a>
</td>
<td>#item.Pr_Email</td>
<td>#item.Pr_ContactNo</td>
</tr>
}
I really am struggling with something that I thought was simple...
I am making a simple search-result table based on $.getJSON call, and want to keep my code as "generic" as possible.
In my (simplified) HTML :
<form id="searchForm">
(...)
<button type="button" onclick="search()">Search</button>
</form>
(...)
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: FOO"></td>
(...)
<td data-bind="text: BAR"></td>
</tr>
</tbody>
Then in my javascript (in script tags lower in the page):
var search = function(){
var form = $('#searchForm');
$.getJSON("php/query/jsonQuery.php?jsonQuery=search", form.serialize(), function(jsonAnswer, textStatus) {
console.log(jsonAnswer);
if(typeof viewModel === 'undefined'){
var viewModel = ko.mapping.fromJS(jsonAnswer);
ko.applyBindings(viewModel);
}
else{
ko.mapping.fromJS(jsonAnswer, viewModel);
}
$('#divResults').show();
// console.log(viewModel)
});
}
This works fine on the first "search" click... but not the following : Error You cannot apply bindings multiple times to the same element.
As you can guess, this very ugly "if" testing viewModel is a desperate attempt to get rid of that error.
I've tried many things but I just can't figure out how to do it properly...
I've read this Knockout JS update view from json model and this KnockoutJs v2.3.0 : Error You cannot apply bindings multiple times to the same element but it didn't help me much... maybe because the search() function isn't called on load (and indeed shouldn't be).
Any KO master to give me a clue? Thanks in advance for your help!
This is how I would be approaching what you are trying to accomplish.
var searchService = {
search: function(form, vmData) {
//$.getJSON("php/query/jsonQuery.php?jsonQuery=search", form.serialize(), function(jsonAnswer, textStatus) {
var jsonAnswer = [{
FOO: "Item 1 Foo",
BAR: "Item 1 Bar"
}, {
FOO: "Item 2 Foo",
BAR: "Item 2 Bar"
}]
ko.mapping.fromJS(jsonAnswer, [], vmData);
//})
}
};
var PageViewModel = function() {
var self = this;
self.data = ko.observableArray();
self.hasResults = ko.pureComputed(function() {
return self.data().length > 0;
});
self.search = function() {
var form = $('#searchForm');
searchService.search(form, self.data);
};
};
ko.applyBindings(new PageViewModel());
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<form id="searchForm">
<button type="button" data-bind="click:search">Search</button>
</form>
<div data-bind="visible: !hasResults()"><b>No Results</b></div>
<div data-bind="visible: hasResults">
<table class="table">
<thead>
<tr>
<td>FOO</td>
<td>BAR</td>
</tr>
</thead>
<tbody data-bind="foreach: data">
<tr>
<td data-bind="text: FOO"></td>
<td data-bind="text: BAR"></td>
</tr>
</tbody>
</table>
</div>
<br/>
<pre><code data-bind="text: ko.toJSON($root)"></code></pre>
In the index.cshtml I display the todo items with partial view:
<table class="table table-striped">
#foreach (var todo in Model.Todos)
{
<tr id="#todo.TodoId">
#{Html.RenderPartial("_TodoModel", todo);}
</tr>
}
The partial view:
<td class="col-md-1">#Html.CheckBoxFor(m => m.Complete, new { #class = "bigCheckbox", id = "completedFlag"+Model.TodoId })</td>
<td class="col-md-9">
<span class="#(Model.Complete ? "completed" : "notCompleted")">
#Model.TodoText
</span>
</td>
<td class="col-md-2 text-center">
#Html.ActionLink("Remove Todo", "Remove", "Todo", new { todoId = Model.TodoId }, new { #class = "btn btn-sm btn-danger" })
</td>
When the checkbox is checked the javascript is calling the controller and updating the completed flag, then redirecting to Index page again. The reload happens, the new css class applied to the element, but the style remains the old one.
Even on soft refresh the new style is applied.
I can change the style with javascript, just curious about why is above behaviour?
Also I feel a bit cleaner if the model changes the style should also change.
In a declarative dojox.grid.datagrid, am using onresizecolumn in table tag.
onresizecolumn="columnResize(this.id,this.cellIdx)"
onresizecolumn calls a function. on resizing particular column i want to get the cellIdx.
<div class="claro" id="eterte" name="dataGrid" onclick="getConnect('inner__eterte');setWidgetproperty(this.id,'xy','inner__eterte');" ondblclick="editCustomGrid(this.id)" onmouseup="setDocStyle(this.id)" style="height:200px; left:39px; position:absolute; top:251px; width:950px;">
<table class="claro" dojotype="dojox.grid.DataGrid" id="inner__eterte" onresizecolumn="columnResize(this.id,this.cellIdx)" rowselector="10px" style="height: 180px; width: 400px;">
<thead>
<tr>
<th field="Column1" id="Column1_6" width="159px">
Column1
</th>
</tr>
</thead>
</table>
<input id="hidden__eterte" name="dataGrid" style="display:none;" type="hidden">
</div>
function columnResize(id,index){
alert();
alert(id);
alert(index);
}
By reading the API documentation I come to the conclusion that Dojo automatically sends the Cell index to the event handler. So the solution is by simply providing the following attribute onResizeColumn="myFunction" and then you define a function like this:
function myFunction(cellDx) {
alert(cellDx);
}
This should work, I even made a JSFiddle to test it. By the way, is there any reason why you would like to do all of it in a declarative way? As far as my experience goes, it's a lot easier to write most of this in JavaScript.
I can get it working this way, not sure if it's a best practice.
http://jsfiddle.net/gE8rH/6/
HTML (removed onresizecolumn attribute):
<div class="claro" id="eterte" name="dataGrid" onclick="getConnect('inner__eterte');setWidgetproperty(this.id,'xy','inner__eterte');" ondblclick="editCustomGrid(this.id)" onmouseup="setDocStyle(this.id)" style="height:200px; width:950px;">
<table dojotype="dojox.grid.DataGrid" id="inner__eterte" rowselector="10px" style="height: 180px; width: 400px;">
<thead>
<tr>
<th field="Column1" id="Column1_6" width="109px">Column1</th>
<th field="Column2" id="Column1_7" width="109px">Column2</th>
<th field="Column2" id="Column1_8" width="109px">Column3</th>
</tr>
</thead>
</table>
</div>
JS (using Dojo 1.7+ module names), assign to the widget's onResizeColumn property:
require(["dojo/parser", "dijit/registry", "dojox/grid/DataGrid"], function (parser, registry) {
parser.parse().then(afterParse);
function afterParse() {
var d = registry.byId("inner__eterte");
console.log(d);
d.onResizeColumn = function (colIdx) {
console.log("columnResize");
console.log("args", arguments);
console.log("this", this);
console.log("colIdx", colIdx);
};
}
});
Outputs this when resizing the first column:
columnResize
args [0]
this [Widget dojox.grid.DataGrid, inner__eterte] { _attachPoints=[4], _attachEvents=[1], _connects=[0], more...}
colIdx 0
I have the following block(s) of code that is copy and pasted about 5 different times within a razor view. It basically displays a table for the same model just with different data.
How can I re-write it as an html helper or lambda func so that I can reuse it for n different models that are passed into the view?
// Example for Model.A and Model.B
var cCols = new[] { "val1", "val2"};
// Display the data for A
<div class="group-property">
<div class="group-label">Title A</div>
<table class="collection-table">
<thead>
<tr class="collection-head">#foreach (var col in cCols) {<th scope="col">#col</th>}</tr>
</thead>
<tbody>
#foreach (var item in Model.A)
{
<td>#item.val1</td>
<td>#item.val2</td>
}
</tbody>
</table>
</div>
// Display the data for B
<div class="group-property">
<div class="group-label">Title B</div>
<table class="collection-table">
<thead>
<tr class="collection-head">#foreach (var col in cCols) {<th scope="col">#col</th>}</tr>
</thead>
<tbody>
#foreach (var item in Model.B)
{
<td>#item.val1</td>
<td>#item.val2</td>
}
</tbody>
</table>
</div>
I don't know if I get it right, but why not use #Html.Partial ?
Andrei Neagu
I'm not sure what you've tried, but to make it an helper function you just need to take the pieces that vary and make them parameters to the function:
#helper MyHelperFunction(string title, IEnumerable<ItemClass> items)
{
var cCols = new[] { "val1", "val2"};
<div class="group-property">
<div class="group-label">#title</div>
<table class="collection-table">
<thead>
<tr class="collection-head">#foreach (var col in cCols) {<th scope="col">#col</th>}</tr>
</thead>
<tbody>
#foreach (var item in items)
{
<td>#item.val1</td>
<td>#item.val2</td>
}
</tbody>
</table>
</div>
}
#MyHelperFunction("Title A", Model.A)
#MyHelperFunction("Title B", Model.B)