MVC Client-Side Validation for EditorFor in foreach - javascript

So I have a view with the following structure (this isn't the actual code, but a summary):
#using (Html.BeginForm("Action", "Controller", FormMethod.Post))
{
#Html.ValidationSummary("", new { #class = "text-danger" })
<table>
<thead>
<tr>
<th>Column1</th>
<th>Column2</th>
</tr>
</thead>
<tbody id="myTableBody">
#for (int i = 0; i < Model.Components.Count; i++)
{
#Html.EditorFor(m => m.MyCollection[i])
}
</tbody>
<tfoot>
<tr>
<td>
<button id="btnAddRow" type="button">MyButton</button>
</td>
</tr>
</tfoot>
</table>
<input type="button" id="btnSubmit" />
}
#section scripts {
#Scripts.Render("~/Scripts/MyJs.js")
}
The EditorFor is rendering markup that represents rows bound to properties in MyCollection. Here's a sample snippet of how the editor template looks:
#model MyProject.Models.MyCollectionClass
<tr>
<td>
#Html.TextBoxFor(m => m.Name)
</td>
<td>
#Html.DropDownListFor(m => m.Type, Model.AvailableTypes)
</td>
</tr>
Basically, my problem is that the client-side validation won't fire for the elements inside of the editor template as it should. Could someone point me in the right direction of where I might be going wrong with this.
Also, please note that the following is set in my web.config.
<appSettings>
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
</appSettings>
and also MyCollectionClass has proper [Require] annotations on the properties that shall be enforced. Another thing to note is that checking
if(ModelState.IsValid)
{
}
Returns false, as expected, if the required fields aren't right. The problem there is that I want client-side validation and not server-side. One of my other pages is implementing jQuery validation, but does not contain all the nesting that this scenario does and thus it works correctly.
Thanks in advance.

From what I Have learnt MVC Doesn't really provide 'out of the box' client side validation.
there are third party options but I prefer to do my own thing so I handballed the whole thing in JavaScript.
To compound matters with the EditorFor doesn't allow adding html attributes like other Helper Methods.
My Fix for this was fairly complex but I felt comprehensive, I hope you find it as helpful as I have
First Overload the HtmlHelper EditorFor in .Net
public static HtmlString EditBlockFor<T, TValue>(this HtmlHelper<T> helper, Expression<System.Func<T, TValue>> prop, bool required)
{
string Block = "";
Block += "<div class='UKWctl UKWEditBox' " +
"data-oldvalue='" + helper.ValueFor(prop) + "' " +
"data-required='" + required.ToString() + ">";
Block += helper.EditorFor(prop);
Block += "</div>";
return new HtmlString(Block);
}
add the new editBlockfor in razor view (just as you are) but alter the Begin Form method to add a Name and id to the form element so you can identify it later
#using (Html.BeginForm("Action", "Controller", FormMethod.Post, new { name = "MyDataForm", id = "MyDataForm" }))
Then when the User Clicks Save, Run the validation method from JavaScript
function validate(container) {
var valid = true;
//use jquery to iterate your overloaded editors controls fist and clear any old validation
$(container).find(".UKWctl").each(function (index) { clearValidation($(this)); });
//then itterate Specific validation requirements
$(container).find(".UKWctl[data-required='True']").each(function (index) {
var editobj = getUKWEdit(this);
if (editobj.val() == "") {
valid = false;
//use this Method to actually add the errors to the element
AddValidationError(editobj, 'This field, is required');
//now add the Handlers to the element to show or hide the valdation popup
$(editobj).on('mouseenter', function (evt) { showvalidationContext(editobj, evt); });
$(editobj).on('mouseout', function () { hidevalidationContext(); });
//finally add a new class to the element so that extra styling can be added to indicate an issue
$(editobj).addClass('valerror');
}
});
//return the result so the methods can be used as a bool
return valid;
}
Add Validation Method
function AddValidationError(element, error) {
//first check to see if we have a validation attribute using jQuery
var errorList = $(element).attr('data-validationerror');
//If not Create a new Array()
if (!errorList || errorList.length < 1) {
errorList = new Array();
} else {
//if we have, parse the Data from Json
var tmpobj = jQuery.parseJSON(errorList);
//use jquery.Map to convert it to an Array()
errorList = $.map(tmpobj, function (el) { return el; });
}
if ($.inArray(error, errorList) < 0) {
// no point in add the same Error twice (just in case)
errorList.push(error);
}
//then stringyfy the data backl to JSON and add it to a Data attribute on your element using jQuery
$(element).attr('data-validationerror', JSON.stringify(errorList));
}
Lastly Show and hide the actual Errors,
In order to facilitate this easily I slipped in a little div element to the _Layout.html
<div id="ValidataionErrors" title="" style="display:none">
<h3 class="error">Validation Error</h3>
<p>This item contatins a validation Error and Preventing Saving</p>
<p class="validationp"></p>
</div>
Show
var tipdelay;
function showvalidationContext(sender, evt)
{
//return if for what ever reason the validationError is missing
if ($(sender).attr('data-validationerror') == "") return;
//Parse the Error to an Object
var jsonErrors = jQuery.parseJSON($(sender).attr('data-validationerror'));
var errorString = '';
//itterate the Errors from the List and build an 'ErrorString'
for (var i = 0; i <= jsonErrors.length; i++)
{
if (jsonErrors[i]) {
//if we already have some data slip in a line break
if (errorString.length > 0) { errorString += '<br>'; }
errorString += jsonErrors[i];
}
}
//we don't want to trigger the tip immediatly so delay it for just a moment
tipdelay = setTimeout(function () {
//find the p tag tip if the tip element
var validationError = $('#ValidataionErrors').find('.validationp');
//then set the html to the ErrorString
$(validationError).html(errorString);
//finally actually show the tip using jQuery, you can use the evt to find the mouse position
$('#ValidataionErrors').css('top', evt.clientY);
$('#ValidataionErrors').css('left', evt.clientX);
//make sure that the tip appears over everything
$('#ValidataionErrors').css('z-index', '1000');
$('#ValidataionErrors').show();
}, 500);
}
Hide (Its much easier to hide)
function hidevalidationContext() {
//clear out the tipdelay
clearTimeout(tipdelay);
//use jquery to hide the popup
$('#ValidataionErrors').css('top', '-1000000px');
$('#ValidataionErrors').css('left', '-1000000px');
$('#ValidataionErrors').css('z-index', '-1000');
$('#ValidataionErrors').hide();
}
for usage you can try some thing like
function save()
{
if (validate($("#MyDataForm")))
{
$("#MyDataForm").submit();
}
else {
//all the leg has been done this stage so perhaps do nothing
}
}
here is My Css for the validation Popup
#ValidataionErrors {
display: block;
height: auto;
width: 300px;
background-color: white;
border: 1px solid black;
position: absolute;
text-align: center;
font-size: 10px;
}
#ValidataionErrors h3 { border: 2px solid red; }
.valerror { box-shadow: 0 0 2px 1px red; }

Related

Button within modal's table not firing [duplicate]

This question already has answers here:
Event binding on dynamically created elements?
(23 answers)
Closed 3 years ago.
Attempted to put a delete button that works in a table into a modal, with a table and it's like the click event is not firing at all. Hit's not back end code, no console.log(s), or js break points. Am I missing something?
Modal's Table
<table class="table table-hover table-md ">
<thead>
<tr>
<td class="text-left TableHead">Role</td>
<td class="text-right TableHead">Delete</td>
</tr>
</thead>
#*--Table Body For Each to pull DB records--*#
<tbody>
#foreach (var role in Model.Roles)
{
<tr>
<td>#role</td>
<td>
<button class="sqButton btnRed float-right zIndex"
title="Delete" data-toggle="ajax-modal" data-target="#deleteRoleUser"
data-url="#Url.Action("Delete", "Administration",
new {Id = Model.Id , Type = "roleUser"})" >
<i class="glyphicon glyphicon-remove"></i>
</button>
</td>
</tr>
}
</tbody>
</table>
Controller that it's supposed to call
[HttpGet]
public async Task<IActionResult> Delete(string id, string type)
{
if (type == "user") {
ViewBag.Type = "user";
var user = await userManager.FindByIdAsync(id);
if (user == null)
{
ViewBag.ErrorMessage = $"User with Id = {id} cannot be found";
return View("NotFound");
}
var model = new EditUserViewModel
{
Id = user.Id,
UserName = user.UserName,
};
ViewBag.UN = user.UserName;
return PartialView("~/Views/Modals/_DeleteModalPartial.cshtml", model);
}
if (type == "roleUser")
{
ViewBag.Type = "roleUser";
var role = await roleManager.FindByIdAsync(id);
if (role == null)
{
ViewBag.ErrorMessage = $"Role with Id = {id} cannot be found";
return View("NotFound");
}
var model = new EditRoleViewModel
{
Id = role.Id,
RoleName = role.Name,
};
ViewBag.Role = role.Name;
return PartialView("~/Views/Modals/_DeleteModalPartial.cshtml", model);
}
else
{
ViewBag.ErrorMessage = $"cannot be found";
return View("NotFound");
}
}
I am not sure why the click event on the button is not working at all. I have tried removing random code and literally nothing is making it go over to the controller at the least.
EDIT added javascript
$(function () {
var placeholderElement = $('#modal-placeholder');
$('[data-toggle="ajax-modal"]').click(function (event) {
var url = $(this).data('url');
$.get(url).done(function (data) {
placeholderElement.html(data);
placeholderElement.find('.modal').modal('show');
});
});
});
$('.sqButton').click( function (event) {
event.stopPropagation();
});
Since the button doesn't exist on page load you will have to create a event delegate to something that does exist on page load that will attach the event to the right element when it finally does appear in the DOM
In this case we will use the document (because it always exists on page load, some people use 'body') to delegate the event to the [data-toggle="ajax-modal"], like this:
$(document).on('click', '[data-toggle="ajax-modal"]', function (event) {
// code here
});
This will attach the event to the [data-toggle="ajax-modal"] elements on page load AND after page load if the element gets added later.
Try replacing your javascript code
$('.sqButton').click( function (event) {
event.stopPropagation();
});
With the following code
$('.sqButton').click(function(event) {
var url = $(this).data('url');
$.get(url).done(function (data) {
placeholderElement.html(data);
placeholderElement.find('.modal').modal('show');
});
});
if you manually force click, does it hit your controller?
document.querySelector('.btnRed').click();
is there any other element(s) "hijacking" click event?

how can i compare class to a string in js

I want to add toggle star functionality in my project. For which I'm calling this script on click. This code fails to compare value of starclass to the class defined in the string.
Here i m trying to add star/unstar functionality just like gmail messages to my project.
$(".mailbox-star").click(function (e) {
debugger;
e.preventDefault();
var $this = $(this).find("a > i");
var glyph = $this.hasClass("glyphicon");
var fa = $this.hasClass("fa");
var msgId = $("#MsgId").val();
var StarClass = $(".mailbox-star i").attr('class');
var StarStatus;
if (StarClass === "fa text-yellow fa-star-o") {
StarStatus = true;
} else {
StarStatus = false;
}
//var starstatus = document.getElementById('ReadstatusStarred');
if (glyph) {
$this.toggleClass("glyphicon-star");
$this.toggleClass("glyphicon-star-empty");
}
$.ajax({
url: "/Home/Starred",
type: "GET",
dataType: "json",
data: {
ChangeStarredStatus: StarStatus,
ChangeMessageId: msgId
},
success: function (status) {
if (status) {
alert(status);
if (fa) {
$this.toggleClass("fa-star");
$this.toggleClass("fa-star-o");
}
}
},
error: function () {
alert("starfailed1");
}
})
});
//HTML CODE
here i m fetching values from my controller using model .If I can send value of IsStarred parameter in my js code my problem will be sorted
<table class="table table-hover table-striped">
<tbody>
#{int count = 0;}
#foreach (var item in Model)
{
string[] dt = #item.DateTime.ToString().Split(' ');
<tr title="#item.DateTime" id="ReadMessage" class="#((item.IsRead != true) ? "row row-highlight" : "row")" >
<td><input type="hidden" value="#item.IsRead" id="Readstatus_#count"></td>
<td><input type="hidden" value="#item.IsStarred" id="ReadstatusStarred"></td>
<td><input type="hidden" id="MsgId" value="#item.MessageId" /></td>
<td><input type="checkbox"></td>
<td class="mailbox-star" ><i class="#((item.IsStarred==true)? "fa fa-star text-yellow":"fa text-yelow fa-star-o")"></i></td>
<td class="mailbox-name" id="Text1" onclick="location.href='#Url.Action("Read", "Home", new
{
NameRead = item.FullName,
SubjectRead = item.Subject,
BodyRead = item.Body,
DateRead = item.DateTime,
MessageIdRead= item.MessageId,
})'">
<a href="#" id="Name">
#item.FullName
</a>
</td>
<td class="mailbox-subject" id="Text1">
<b>#item.Subject</b>-
#if (item.Body == null || item.Body.Length == 0)
{
}
else
{
if (item.Body.Length >= 100)
{
#item.Body.Substring(0, 100)
}
else
{
#item.Body
}
}
</td>
<td class="mailbox-attachment"></td>
<td class="mailbox-date">
#dt[0]
</td>
</tr>
count++;
}
</tbody>
</table>
</div>
Try using jQuery's is() to check for classes instead
var StarStatus = $(".mailbox-star i").is('.fa, .text-yellow, .fa-star-o')
If I got your description right you wanna have something like gmail has, click to star a mail, click again to destar it?
It's hard to say what is broken without the HTML you are using but I would do this in the following manner:
When loading mails from back you have to set up class "starMarked" to starred mails depending on how you mark the starred mail in the data comming from back you would check if something is true or equal to some value and then .addClass("starMarked") to that element.
bind the .click(Function that does the logic below) to all elements that represent mail (list member, square, icon, depends on what you have in the UI)
A functionality of that click then checks if that mail is stared or not. Since the status is already represented with class no need to check through data pulled from the server or make an additional request to the server to get that email's status. This saves time and load on the server.
NOTE: You must be certain the request to change status on the server went through or your logic of toggling on front and status on backend might become inconsistent!
Toggle on front could be done numerous ways but simplest would be using the CSS class "starMarked" to represent it's starred and lack of it to signal it's not. It gives 2 birds with one stone (looks and logic). If you need to check the status you could use .hasClass("starMarked").
When toggling a class use .removeClass() to remove the class from the element

Why won't JS / JQuery read the text box values?

I have looked all over for a reason behind why this code does not work and I am stumped.
I have an ASPX page with C# code behind. The HTML mark-up has a JQuery dialog that functions properly. When the submit button is clicked the dialog closes and the data is passed to a web exposed method and is written to the database. All values are saved for the ddl and chkbox controls but the string value of the text box is empty. The database is set to NOT NULL for the field the text box is populating and the data is being saved so I know data is being passed but it is not the value entered into the text box.
The text box ID is txtCategoryName and the Client ID mode is set to static. I have tried to get the values using the following:
var CategoryName = $('#txtCategoryName').val();
var CategoryName = $('#txtCategoryName').text();
var CategoryName = $(document.getElementById('txtCategoryName')).text();
var CategoryName = $(document.getElementById('txtCategoryName')).val();
var CategoryName = document.getElementById('txtCategoryName').value;
All of these return the same blank field. I tried them one at a time.
Currently I am using this JS Code:
$(document).ready(function () {
var CategoryDialog = $(".EditCategories");
var BtnNew = $("#btnNew");
var CatDDL = document.getElementById("ddlCategoryParent3");
var CatChk = $("#chkCatActive").val();
var CategoryID = 0;
var CategoryName = $("#txtCategoryName").val();
var ParentID = CatDDL.options[CatDDL.selectedIndex].value;
if (CatChk) { CatChk = 1; } else { CatChk = 0; }
var CatDialog = $(CategoryDialog.dialog({
maxHeight: 1000,
closeOnEscape: true,
scrollable: false,
width: 650,
title: 'Category Editor',
autoOpen: false,
buttons: [
{
width: 170,
text: "Save",
icons: {
primary: "ui-icon-disk"
},
click: function () {
$(this).dialog("close");
window.alert(PageMethods.saveCat(CategoryName, ParentID, CategoryID, CatChk));
}
},
{
width: 170,
text: "Delete",
icons: {
primary: "ui-icon-circle-minus"
},
click: function () {
$(this).dialog("close");
}
},
{
width: 170,
text: "Cancel",
icons: {
primary: "ui-icon-circle-close"
},
click: function () {
$(this).dialog("close");
}
}
]
})
);
BtnNew.click(function () {
$(CatDialog).dialog('open');
$(CatDialog).parent().appendTo($("form:first"));
});
});
The code markup for the aspx page (categories.aspx)
<div class="EditCategories">
<div class="Table">
<div class="TableRow">
<div class="TableCell">
<div class="TextBlock220">Category Name </div>
</div><!-- End Table Cell -->
<div class="TableCell">
<input id="txtCategoryName" class="ControlTextBox" />
<!--<asp:TextBox ID="txtCategoryName" CssClass="ControlTextBox" runat="server" ClientIDMode="Static"></asp:TextBox>-->
</div><!--End Table Cell-->
</div><!-- End Row 1 -->
<div class="TableRow">
<div class="TableCell">
Parent Category
</div><!-- End Table Cell -->
<div class="TableCell">
<asp:DropDownList ID="ddlCategoryParent3" runat="server" CssClass="ControlDropDownList" ClientIDMode="Static"></asp:DropDownList>
</div><!--End Table Cell-->
</div>
<div class="TableRow">
<div class="TableCell">
Active
</div><!-- End Table Cell -->
<div class="TableCell">
<asp:Checkbox ID="chkCatActive" CssClass="ControlCheckBox" runat="server" ClientIDMode="Static"></asp:Checkbox>
</div><!--End Table Cell-->
</div><!-- End Row 3-->
</div>
</div>
The C# Code behind method for the ASPX page:
[System.Web.Services.WebMethod()]
[System.Web.Script.Services.ScriptMethod()]
public static string saveCat(string _Name_, int _parent_id_, int ID, int _Status_)
{
Category eCT = new Category();
eCT.CategoryName = _Name_;
eCT.ParentID = _parent_id_;
eCT.ID = ID;
eCT.Status = _Status_;
eCT.Save();
return eCT.resultMessage;
}
And the save method:
/// <summary>
/// If the ID = 0 the data is written as a new category.
/// If the ID is greater than 0 the data is updated.
/// </summary>
/// <returns>The objects result value will hold the result of the attempt to update data as type Boolean. The objects resultMessage value will contain the string result of the attempt to add data.</returns>
public void Save()
{
result = dl.CategoryExists(this);
if (result) { resultMessage = "The parent category already contains a category named " + CategoryName.Trim(); }
else {
if (ID > 0)
{
if (!result) { resultMessage = "There was an unexpected error updating " + CategoryName.Trim() + ". No changes were saved."; }
}
else
{
result = dl.InsertCategory(this);
if (!result) { resultMessage = "There was an unexpected error creating the Category."; }
}
}
if (result) { resultMessage = "New Category Successfully Created"; }
}
Any help is greatly appreciated thanks.
The issue here is you're attempting to get the value right as soon as the page loads, before the input field gets filled out. Place this code inside the button click function:
var CategoryName = document.getElementById('txtCategoryName').value;
and it should work for you. If not, let us know.
Your code should look something like this:
click: function () {
// note: CategoryID not used yet.
var CategoryName = $("#txtCategoryName").val();
var CatChk = $("#chkCatActive").val();
var CatDDL = document.getElementById("ddlCategoryParent3")
var ParentID = CatDDL.options[CatDDL.selectedIndex].value;
if (CatChk) { CatChk = 1; } else { CatChk = 0; }
$(this).dialog("close");
window.alert(PageMethods.saveCat(CategoryName, ParentID, CategoryID, CatChk));
}
You are fetching the values from your dialog at page startup time BEFORE they have been edited.
It looks like this:
var CategoryName = $("#txtCategoryName").val();
is run at page startup time before the page has been edited. This will fetch the default value for the input field and will never reflect any editing that is done on the page. The line of code above does not create a "live" connection with the input field on the page. It just gets the value at the time that line of code is run and from then on there is no connection to any edits made to the field.
I would think you want to fetch the value only later when you actually need to value for something. In general, you do not want to cache a value like this because the cached value gets out of sync with what is in the actual field on the page. Just fetch it at the very moment that you need it for something and it will never have a stale value.
If the place that you're using this value is inside the dialog click handler, then fetch it there so you are getting the latest value:
click: function () {
$(this).dialog("close");
var CatChk = $("#chkCatActive").val() ? 1 : 0;
var CategoryName = $("#txtCategoryName").val();
var CatDDL = document.getElementById("ddlCategoryParent3");
var ParentID = CatDDL.options[CatDDL.selectedIndex].value;
window.alert(PageMethods.saveCat(categoryName, ParentID, CategoryID, CatChk));
}

Display on a table only certain values from an object

I have an object, say,
var LocSpecs=[{Address:"xxxxxx",SF:"1,200",YearBuilt:"xxxx"},
{Address:"xxxxxxx",SF:"1,950",YearBuilt:"xxxx"}.......}];
Over 200 key/values in the object.
I need to loop through and grab the values that meet a value and display said data on a table.
I get the for() and if(). I just don't quite get the appending the table to show only the records that meet the criteria.
resulting table would display on a
document.getElementBy("xxxx").innerHTML=myTable;
Assuming that you need to display rows based on some value in the object.
Create a function that builds a row when condition is satisfied.
LocSpecs.forEach(function(elm) {
if(elm.SF) { // your criteria
addRow(elm);
}
});
function addRow(elm) {
var table = document.getElementById("myTable");
var row = table.insertRow(0);
var cell1 = row.insertCell(0);
var cell2 = row.insertCell(1);
cell1.innerHTML = elm.Address;
cell2.innerHTML = elm.YearBuilt;
}
I give you a ES6 alternative method to make a table. Using the DOMTableElement interface is inherently slower when used in JavaScript then adding pure HTML to the page and letting the browser's parser do it (That is what browsers are designed to do after all, parse and display HTML)
It uses Array.filter() to filter the records.
It uses Arrow functions to reduce the amount of code.
It uses Template strings to create the HTML for the table.
var locSpecs=[ // some data with random addresses added
{Address:"691 Union Street Herndon, VA 20170",SF:"1,950",YearBuilt:"1922"},
{Address:"939 Laurel Drive Pawtucket, RI 02860",SF:"1,950",YearBuilt:"1922"},
{Address:"176 Redwood Drive Ankeny, IA 50023",SF:"1,850",YearBuilt:"1932"},
{Address:"621 Sycamore Lane Flint, MI 48504",SF:"1,750",YearBuilt:"1932"},
{Address:"190 Canterbury Court Bountiful, UT 84010",SF:"1,350",YearBuilt:"1922"},
{Address:"461 3rd Street West Coram, NY 11727",SF:"1,950",YearBuilt:"1922"}
]
function createTable(data,filter,cellOrder,elementID,title){
// defined functions needed
function createRow(record){ // create a row
var str = ""; // string to hold the row HTML
cellOrder.forEach( // for each field in order
// want it to look good so add the cell class and the field class
name => str+=`<td class='${fieldClassPrefix+name}' >${record[name]}</td>`
);
return `<tr>${str}</tr>`; // wrap a row tab and return the new HTML for row
}
function createHeader(){ // created the header
var str = ""
cellOrder.forEach(name => str+= `<td class='${fieldClassPrefix+name}' >${name}</td>`);
return `<thead><tr>${str}</tr></thead>`;
}
// define variableds
var HTML, tableContainerElement,fieldClassPrefix; // you should not see any var tokens past this point
HTML = ""; // string to hold the new table HTML
fieldClassPrefix = "FF_"; // prfix for field class names
// find the element that will contain the new table
tableContainerElement = document.getElementById(elementID);
if(tableContainerElement !== null){ // make sure we found it
HTML += `${createHeader()}<tbody>`; // Add the header and begin the body
data.filter(record => filter(record)) // filter records
.forEach(record => HTML+=createRow(record)); // add filteredrecords
// now put it all together and put it on the page
tableContainerElement.innerHTML = `<div>${title}</div><table>${HTML}</tbody></table>`;
return true; // incase you need to know it worked
}
return false;
}
// create the click events and have them create the tables they need
document.getElementById("But1").addEventListener("click",
function(){createTable(
locSpecs,
function(record){
return Number(record.SF.replace(",","")) < 1800;
},
"SF,YearBuilt,Address".split(","),
"tableContainer",
"Records with a SF under 1800"
);}
);
document.getElementById("But2").addEventListener("click",
function(){createTable(
locSpecs,
function(record){
return Number(record.YearBuilt) > 1922;
},
"SF,YearBuilt".split(","),
"tableContainer",
"Records built after 1922"
);}
);
document.getElementById("But3").addEventListener("click",
function(){createTable(
locSpecs,
function(record){
return record.YearBuilt === "1922";
},
"SF,Address".split(","), // Dont need year
"tableContainer",
"Records built in 1922"
);}
);
// show the default table
createTable( // display all records and fields
locSpecs,
function(){return true; }, // filter function true for all
"Address,SF,YearBuilt".split(","),
"tableContainer",
"All records."
);
input {
cursor:pointer;
}
but:hover {
background:#7dF;
}
div {
width:100%;
}
table {
border:black 2px solid;
width:100%;
}
thead {
font-weight:bold;
text-align:center;
border:black 1px solid;
}
thead .FF_SF {
background:#dfffdf;
text-align:center;
}
thead .FF_YearBuilt {
background:#efdfff;
text-align:center;
}
thead .FF_Address {
background:#ffefdf;
text-align:center;
}
.FF_Address {
background:#ffddcc;
}
.FF_SF {
background:#ccffdd;
text-align:center;
}
.FF_YearBuilt {
background:#ddccff;
text-align:right;
}
<div>Select an option "Sorry IE ECMAScript6 browsers only."</div>
<input value="SF less than 1800" id="But1">
<input value="Year Over 1922" id="But2">
<input value="Year 1922" id="But3">
<div id='tableContainer'>

HTML.Action Loop results in wrong model on page (variable scope pollution?)

I have a view (cshtml) that has a tab strip on it. The contents of each tab is of course different. The individual tabs have the correct data/information on them. There is some javascript that is intended to fire when a selection is made from the control on the individual tab. As it stands right now the first tab rendered the javascript fires. All other tabs do not fire. Further on the tab that does fire (first one) it obtains the correct value but then when trying to find the matching item in the model it doesn't find a match. Debugging shows that only the data for the last tab is available in the model. Well that explains why no match but begs the question of where did the data the first page was populated with go?
I have snipped the code for brevity. If, in my ignorance I left something out just say so and I'll post whatever is needed.
So to start here is the parent cshtml:
foreach (var extbrd in Model.ExternalBoards)
{
tabstrip.Add()
.Text(extbrd.ExtForumName)
.ImageUrl("~/.../ForumTabIcon.png")
.Content(#<text>
<div>
#Html.Action("ActionName", "Controller", new { id = extbrd.BoardId });
</div>
</text>);
}
Well as you can see above as we loop we call an action in the controller for each tab. Here is that action:
public ActionResult ActionName(int extforumid)
{
//get url for selected forum (tab) and pull feed
ExternalForums ExtFrm = _forumService.GetExternalForumById(extforumid);
reader.Url = ExtFrm.ForumUrl;
return View(reader.GetFeed());
}
That's actually it. As above I can post the reader code but I don't think it is the source of the trouble.
Well this action of course has a view and this is where I think things get wacky:
#model ExternalThreadsModel
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model.RssThreads))
</script>
<script type="text/javascript">
$(function() {
$("##Html.FieldIdFor(model => model.ExtForumIds)").click(function () {
var selectedItem = $(this).val();
var matchingObj = getObjects(model, 'ThreadValue', selectedItem);
if(matchingObj > 0)
{
var $iframe = $('#ForumFrame');
if ( $iframe.length ) {
$iframe.attr('src', matchingObj[0].Link);
}
var $prevfram = $('#ForumPreview');
if ( $prevfram.length ) {
$prevfram.val(matchingObj[0].Description);
}
}
});
});
function getObjects(obj, key, val) {
var objects = [];
for (var i in obj) {
if (!obj.hasOwnProperty(i)) continue;
if (typeof obj[i] == 'object') {
objects = objects.concat(getObjects(obj[i], key, val));
} else if (i == key && obj[key] == val) {
objects.push(obj);
}
}
return objects;
}
</script>
<div>
<table>
<tr>
<td>
#Html.DropDownListFor(model => model.ExtForumIds, Model.SelectThreads, new {style = "...", #size = 30})
</td>
<td style="width:25px;"> </td>
<td>
#{ Html.Telerik().TabStrip()
.Name("ForumView")
.Items(tabstrip =>
{
tabstrip.Add()
.Text("Preview")
.Content(#<text>
<div>
<textarea style="background-color:#979797; text-decoration: none;" id="ForumPreview" name="ForumPreview" rows="26" cols="200" readonly></textarea>
</div>
</text>);
tabstrip.Add()
.Text("Interactive")
.Content(#<text>
<div>
<iframe id="ForumFrame" name="ForumFrame" src="" style="width:800px;height:350px;"></iframe>
</div>
</text>);
})
.SelectedIndex(0)
.Render();
}
</td>
</tr>
</table>
</div>
So as I mentioned each tab does have the correct data / information on it. The problem comes when a user selects an item from the drop down list.
The click handler only fires on the first tab. It doesn't fire for any other tabs???
Further on the first tab the click handler does fire and it pulls the correct selectedItem but when it runs through the helper function getobjects it doesn't find a match.
When I break and examine "model" as it is being passed into getObjects it only contains data for the last tab...so yeah nothing is going to be matched.
What is even stranger for me to understand is the line:
<script type="text/javascript">
var model = #Html.Raw(Json.Encode(Model.RssThreads))
</script>
In HTML it does render a json object with ALL the data from ALL the tabs...so...somewhere I must be running into variable scope pollution????
Your support and assistance is..as always..greatly appreciated.

Categories