I want to load some html data dynamically from the server (like a grid composed by lots f and ) using jQuery.
At the moment I load it like this:
$("#Ricerca_Div_ContenitoreRisultati table tbody").load("/Segnalazioni/CercaSegnalazioni/3");
and generate it like this:
public ActionResult CercaSegnalazioni(int flag, string sort)
{
[..]
XElement SegnalazioniTrovate = Models.Segnalazioni.Recupera(flag, sortVal);
string ritorno = "";
bool alterna = false;
foreach (XElement segnalazione in SegnalazioniTrovate.Elements("dossier"))
{
ritorno += alterna? "<tr>" : "<tr class = \"alternata\">";
ritorno += String.Format(#"
<td><span style=""white-space: nowrap"">{0}</span></td>
<td><span style=""white-space: nowrap"">{1}</span></td>
<td style =""display : none"">{2}</td>
<td><span style=""white-space: nowrap"">{3}</span></td>
<td><span style=""white-space: nowrap"">{4}</span></td>
<td><span style=""white-space: nowrap"">{5}</span></td>
</tr>",
(string)segnalazione.Element("NUM_DOSSIER") ?? "",
(string)segnalazione.Element("ANAG_RAGSOC_CGN") ?? "",
(string)segnalazione.Element("ID_RIFATT_SEGN0") ?? "",
Tools.DecodificaStatus(int.Parse((string)segnalazione.Element("FLG_STATUS") ?? "")),
Tools.RmuoviTime((string)segnalazione.Element("DT_ACCADIMENTO")?? ""),
(string)segnalazione.Element("COD_RAMO_LUNA") ?? ""
);
alterna = !alterna;
}
return Content(ritorno);
}
Or, simply put, I make up the HTML code server side with a very messy code I don't like and return it back so that it is ready to be used client-side.
Any better / cleaner solution?
Thanks
There's different ways of doing this, and although none of them end up looking perfectly clean, the one that works best for me is to do the HTML construction on the client side. The server can return an object that works well in javascript (let's say, List<Segnalazione>) and then the client-side handler does things like:
$(list).each(function() {
var tr = $('<tr />').append(
$('<td />').css('white-space', 'nowrap').text(this.NUM_DOSSIER)
).append(
$('<td />').css('white-space', 'nowrap').text(this.ANAG_RAGSOC_CGN)
)
$("#Ricerca_Div_ContenitoreRisultati table tbody").append(tr);
});
Obviously, I'm oversimplying your output, but hopefully that gives you the idea.
If nothing else, doing it in jquery gives you the automatic escaping of values within the 'text', 'attr', and 'css' methods rather than the HttpUtility.HtmlEncode, AttributeEncode methods that would clutter up your output in C#
The cleaner solution will be creating separate View and using more CSS:
UPDATED:
In case of Request.IsAjaxRequest() use PartialView:
Controller:
public ActionResult CercaSegnalazioni(int flag, string sort)
{
[..]
XElement SegnalazioniTrovate = Models.Segnalazioni.Recupera(flag, sortVal);
return PartialView("YourPartialView", SegnalazioniTrovate);
}
YourPartialView.ascx:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<XElement>" %>
<% bool alterna = false; %>
<table id="yourTableId">
<% foreach (XElement segnalazione in SegnalazioniTrovate.Elements("dossier")) { %>
<tr class="<%= alterna ? "alternata" : "" %>">
<% alterna = !alterna; %>
<td>
<span><%= (string)segnalazione.Element("NUM_DOSSIER") ?? "" %></span>
</td>
<td>
<span><%= (string)segnalazione.Element("ANAG_RAGSOC_CGN") ?? "" %></span>
</td>
<td class="nodisplay">
<%= (string)segnalazione.Element("ID_RIFATT_SEGN0") ?? "" %>
</td>
<td>
<span><%= Tools.DecodificaStatus(int.Parse((string)segnalazione.Element("FLG_STATUS") ?? "")) %></span>
</td>
<td>
<span><%= Tools.RmuoviTime((string)segnalazione.Element("DT_ACCADIMENTO")?? "") %></span>
</td>
<td>
<span><%= (string)segnalazione.Element("COD_RAMO_LUNA") ?? "" %></span>
</td>
</tr>
<% } %>
</table>
CSS:
table#yourTableId td span {
white-space: nowrap
}
.nodisplay {
display : none
}
Your best bet is probably to de-couple the presentation from the data. This would mean avoiding having any presentation code (ie. HTML generation) in your controller.
There are 2 ways to handle this...
Nested Partial Views
Partial Views can render... other partial views. Consider something like this...
<body> Page
-- <div> Partial View "parent" (does a RenderPartial of these:)
---- <div> Partial View "child" (piece 1) </div>
---- <div> Partial View "child" (piece 2) </div>
The parent partial view simply contains RenderPartials for the children. In addition, each patial view can end up having it's own URL (/controller/parent/, /controller/child-1/, etc.). In jQuery whenever you trap an event that needs to update the UI, you can simply ajax.load the piece you need and plug it into the div.
JSON -> jQuery Render
The other way is you forgo the server creating any presentation code, and you simply have an API that returns JSON. jQuery then accepts the incoming data objects and figures out what to do with them. Depending on the complexity of what needs to be rendered on the client side, this could be easier. This has the advantage of also allowing the same server-side code to be re-used in other ways. The downside is the content won't be indexed by search engines.
Related
I have a view that is rendering multiple partial view inside as follow :
<div>
<table class="EditTable">
#foreach (var view in Model.ViewsToRender)
{
<tr>
<td style="width: 100%;">
#Html.Partial("~/Areas/TC2/Views/Shared/" + view.Name + ".cshtml", view.viewModel)
</td>
</tr>
}
</table>
</div>
Each view is doing almost the same rendering : content and adding a js script to show/hide the block :
#{
var classShow = "show_hide" + Model.Name;
#Html.AddShowScriptConsult(classShow);
}
<div id="viewContent">
<div class="viewTitle">#Model.Name</div>
<i class="#classShow fa fa-minus-circle buttonExtended"></i>
<div>My html content ....</div>
</div>
It is working fine, except that my helper is only called once for the first view, and the other partial view didnt add the script to show/hide the unit. It seems like this line : "#Html.AddShowScriptConsult(classShow);" is only called once for all the partial view, I expected it to be called once for each view, since the call is inside each partial view within "Model.ViewsToRender".
If you need further informations or clarifications don't be afraid to ask, I simplified my code due to company policy but this example represent perfectly my problem.
I am using Ajax.Begin form with a partial view to replace contents of defined target.
like so,
Partial View:
#model string
<fieldset style="width:916px">
<legend class="SearchFiledSet">AMN</legend>
<table>
<tr valign="top">
<td>Notes: </td>
<td>#Html.TextArea("Notes", #Model, 5, 30, new { disabled = "disabled", style = "background: #FFF; border: 1px solid #000;" })</td>
#using (Ajax.BeginForm("AMN", new AjaxOptions { UpdateTargetId = "AMN",
Url = Url.Action("AMN"),
OnBegin = "OnBegin",
OnFailure = "OnFailure",
OnSuccess = "OnSuccess",
OnComplete = "OnComplete"}))
{
<td style="padding-left: 30px">P N:   #Html.TextBox("PN", "", new { size = "15" })
#if(string.IsNullOrEmpty(#Model))
{
<br />
<font color="red">No matching pn was found in the system.</font>
}
</td>
<td style="padding-left: 60px">
<button type = "submit">Search</button>
</td>
}
</tr>
</table>
</fieldset>
Controller:
public PartialViewResult AMN(string PN = null)
{
IPS p=null;
string remarks= " ";
if (PN != null)
{
p = _pS.GetPPN.Trim());
remarks = p != null ? p.Remarks : remarks;
}
return PartialView(((object)remarks));
}
Main View:
<div id="AMN" style="margin-left: 180px">
#Html.Action("AMN")
</div>
The Ajax calls work fine in IE of course, but in Firefox it hits the break point on the controller and correctly posts during the first submit but then nothing will happen after each consecutive submit. Not even the break point will get hit. I have seen a few other posts of people complaining of this same issue a few years ago but none of them had a resolution. Has anyone experienced this issue and found a resolution or have any recommendations of what can be the issue?
There is another Html.BeginForm on the main page that I link my partial view to, but my partial view is outside that form, and I also tried removing the other form and just leaving the ajax one with no luck.
I am using jquery-1.7.2
I think I now understand what is happening based off of general research on the topic rather than directing it to Ajax.BeginForm method. I wanted to basically mimic a the concept of a panel, and be able to just plug in the full form and replace the panel (partial view) with updated data on the ajax call. Well I am not that experienced with ajax or javascript, but it seems that when I rewrite the html the object on the dom is getting replaced too so all focus is lost, hence it worked on one post but not twice.
This was confusing mostly because it worked the way I originally thought it would on Internet Explorer but not Firefox. So in order to make it cross-browser compatible I just used JSON to send back the data to be changed and then registered a function to the OnSuccess call, which will just change the html necessary rather than rebuilding the partial. I wanted to handle the Ajax mostly with the Asp.net MVC framework libraries to keep the code cleaner but I guess this isn't likely to happen unless I abstract out the form contents from the partial.
Here is the changes made for anyone else who runs into this issue:
Controller:
[HttpGet]
public PartialViewResult AMN()
{
string remarks = " ";
return PartialView(((object)remarks));
}
[HttpPost]
public JsonResult AMN(string PN = null)
{
IPS p=null;
string remarks= " ";
if (PN != null)
{
p = _pS.GetP(PN.Trim());
remarks = p != null ? p.Remarks : null;
}
return Json(remarks);
}
PartialView:
#model string
<fieldset style="width:916px">
<legend class="SearchFiledSet">AMN</legend>
<table>
<tr valign="top">
<td>Notes: </td>
<td>#Html.TextArea("Notes", #Model, 5, 30, new { disabled = "disabled", style = "background: #FFF; border: 1px solid #000;" })</td>
#using (Ajax.BeginForm("AMN", "PS", null, new AjaxOptions {OnSuccess = "processData" }, new { id = "AMNForm" }))
{
<td style="padding-left: 30px">PN:   #Html.TextBox("PN", "", new { size = "15" })
#if(string.IsNullOrEmpty(#Model))
{
<br />
<font color="red">No matching pn was found in the system.</font>
}
</td>
<td style="padding-left: 60px">
<button type = "submit">Search</button>
</td>
}
</tr>
</table>
</fieldset>
<s.. type="text/java..">
function processData(data) {
$("#Notes").val(data);
if (!data[0])
alert("data is null")
else
alert("data is not null")
}
</..>
I am creating a model which is intended to hold a table. The table will hold an arbitrary number of rows defined at run-time. I am wondering if there's an easy way to define this in MVC?
Currently all I have is:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CableSolve.Web.Models.Orders.ActivityDetailsModel>" %>
<div id="ActivityDisplay">
<table id="ActivitiesGrid">
</table>
<div id="ActivitiesGridPager"></div>
</div>
In a different model, I create a table which had a static number of rows. This was pretty easy:
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CableSolve.Web.Models.Orders.OrderDetailsModel>" %>
<fieldset class="collapsible">
<legend>
<%= Html.DisplayFor(model => model.OrderDetailsLegendName)%>
</legend>
<table id="OrderDetailsContentTable" class="ContentTable">
<tr>
<td><%= Html.DisplayFor(model => model.OrderID.Name)%></td>
<td><%= Html.DisplayFor(model => model.OrderID.Value)%></td>
<td><%= Html.DisplayFor(model => model.Comment.Name)%></td>
<td><%= Html.DisplayFor(model => model.Comment.Value)%></td>
</tr>
...
So, any ideas on how I could go about having an arbitrary number of rows in ActivitiesGrid?
Basically, my controller is going to get some information at run-time and I want to display the retrieved information in a table, but I do not know how many rows of information I am going to receive.
As a light-weight solution without introducing third-party stuff (for now, if it proves necessary I will):
public class ActivityDetailsModel
{
public List<ActivityRow> ActivityRows = new List<ActivityRow>();
public struct ActivityRow
{
public DateTime? Date;
public string Person;
public string OrderOrTaskID;
public string Activity;
public ActivityRow(DateTime? date, string person, string orderOrTaskID, string activity)
{
Date = date;
Person = person;
OrderOrTaskID = orderOrTaskID;
Activity = activity;
}
}
public ActivityDetailsModel(IEnumerable<Activity> activities)
{
foreach(Activity activity in activities)
{
string orderOrTaskID = activity.TaskID != 0
? activity.OrderID + "-" + activity.TaskID
: activity.OrderID.ToString();
string activityDescription = string.Format("OldValue: {0}, NewValue: {1}", activity.OldValue,
activity.NewValue);
ActivityRows.Add(new ActivityRow(activity.UpdateDateTime, activity.UpdateUsername, orderOrTaskID, activityDescription));
}
}
}
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<CableSolve.Web.Models.Orders.ActivityDetailsModel>" %>
<div id="ActivityDisplay">
<table id="ActivitiesGrid">
<%foreach (var activityRow in Model.ActivityRows)
{%>
<tr>
<td><%= Html.DisplayForModel(activityRow.Date)%></td>
<td><%= Html.DisplayForModel(activityRow.Person)%></td>
<td><%= Html.DisplayForModel(activityRow.OrderOrTaskID)%></td>
<td><%= Html.DisplayForModel(activityRow.Activity)%></td>
</tr>
<%}%>
</table>
<div id="ActivitiesGridPager"></div>
</div>
Is this relatively understandable? I hate the mark-up with all the angle-brackets and percent signs, but the project hasn't been developer with the alternative syntax.
Since you're after something dynamic, you can take advantage of existing helpers like MVC WebGrid helper or even MvcContrib Grid. These helpers will take care of generating a table containing the columns you want. There's no need to write the table by hand since it'll be dynamically generated during runtime according to the number of objects present in your datasource.
I am having a problem with the <text> tag. The following code will not run due to the </tr> tag near the bottom. If i remove it, it works but it then prints an incorrect table. If i leave it i get the following error: Encountered end tag "tr" with no matching start tag. Are your start/end tags properly balanced?
How can i tell razor to ignore such things?
(I also tried to add a text tag around the /tr and also around all html code but that produces this: Encountered end tag "text" with no matching start tag. Are your start/end tags properly balanced?
#{
int i = 0;
foreach (var item in Model.Model)
{
if (i % 2 == 0)
{
<text><tr class="alternate-row"></text>
}
else
{
<text><tr></text>
}
<td>
<input type="checkbox" />
</td>
<td>
#item.Firstname
</td>
<td>
#item.Surname
</td>
<td>
george#mainevent.co.za
</td>
<td class="options-width">
<a href="" title="Edit" class="icon-2 info-tooltip">
</a><a href="" title="Edit"
class="icon-4 info-tooltip"></a><a href="" title="Edit" class="icon-5 info-tooltip">
</a>
</td>
</tr>
}
}
Update with another question
Why does Razor even test html-tags?
You can do this
<tr#if (i % 2 == 0) { <text> class="alternate-row"</text> }>
or you can set a variable that "holds" your extended html for the <tr> tag like this
int i = 0;
foreach (var item in Model.Model)
{
string ext = "";
if (i % 2 == 0)
{
ext = " class=\"alternate-row\"";
}
<tr#ext>
// ...
Thats the simplest solution, or you can create a custom html helper.
More information: Creating Custom HTML Helpers
Update
Darin said too, what he would create a custom html helper.
I suggest that too, if you need that more than one time.
conclusion
first choice is to create a html helper, second is to use my first
approach (inline if statement) and at last to use a variable.
It does not really depends on "how often" you need that, but if you really need
that only one time, choose the first approach.
Every of the three solutions are correct, its your decision depending on the time
you have.
hope this helps you
<tr#Html.Raw(i % 2 == 0 ? " class=\"alternate-row\"" : "")>
... some tds
</tr>
But personally I would write a custom Html helper to avoid this spaghetti code and have something along the lines of:
#using (Html.Tr(i))
{
<td>
<input type="checkbox" />
</td>
<td>
#item.Firstname
</td>
...
}
I would also refactor and get rid of the foreach loop and replace it with a simple display template call: #Html.DisplayForModel().
I have an alternate approach for you using javascript. Since you are using MVC3, you probably have access to jQuery. Add this little nugget of javascript (See jsFiddle Example)
$(function() {
$('tr:odd').addClass('alternate-row');
})
remove the the <text> blocks from around the tr tag in your conditional
<%foreach (var indication in Model.FindAll(m => m.Model != null && m.Model.Trx != null).OrderBy(m => m.Model.Trx.PrimarySponsor.Company))
{ %>
<tr>
<td><%= indication.DisplayUser %></td>
<td><%= indication.ActiveIndicationUsers[0].FullName %></td>
<td><%= string.IsNullOrEmpty(indication.Model.Trx.PrimarySponsor.Company) ? "Not Yet Saved" : indication.Model.Trx.PrimarySponsor.Company %></td>
<td><%= indication.TimeOpened.ToString(Chatham.Web.Data.Constants.Format.DateTimeSecondsFormatString) %></td>
<td><%= indication.Model.Trx.ProductCollection[0].ProductTypeFriendlyName %></td>
<td><%= (!indication.Model.Trx.ID.HasValue) ? "Not Yet Saved" : indication.Model.Trx.ID.Value.ToString() %></td>
<td><input type="button" value="Open" name="<%= (!indication.Model.Trx.ID.HasValue) ? "Not Yet Saved" : indication.Model.Trx.ID.Value.ToString() %>" /></td>
</tr>
<%} %>
So that above table, as you can see, is dynamically generated. How do I handle the button click? I also want to pass the name attribute of the button into whatever method handles the button click.
Thanks!
You can use the live function of jQuery.
Try this:
$(function(){
$("td input[type=button][value=Open]").live("click", function(e){
var btn = $(this);
alert(btn.attr("name"));
});
})
The same way you would handle a regular button click. Dynamically create the code to handle regular button clicks in the http code you're generating.
That code is tragic and screaming for refactoring. Just looking at it my eyes hurt. You are not encoding strings thus making this code vulnerable to XSS attacks.
So as always in ASP.NET MVC you start with a view model:
public class MyViewModel
{
public string DisplayUser { get; set; }
public string ActiveIndicationsUserFullname { get; set; }
public string Company { get; set; }
[DisplayFormat(DataFormatString = "{0:dd-MM-yyyy}")]
public DateTime TimeOpened { get; set; }
public string TrxId { get; set; }
}
then you will have a controller action which will fetch the model from the repository and map it to the view model. You could use AutoMapper to simplify this mapping. It's in the mapping layer that you will transform everything to be ready to be directly used by the view so that this views doesn't resemble to a horrible tag soup:
public ActionResult Foo()
{
// it's here that you should do the LINQ queries, etc ...
// not in the view. Views are not supposed to fetch any data
// and to be intelligent. Views should be dumb and only render
// the preformatted data that they have been fed by the controller action
IEnumerable<SomeModel> model = ...
IEnumerable<MyViewModel> viewModel = Mapper.Map<IEnumerable<SomeModel>, IEnumerable<MyViewModel>>(model);
return View(viewModel);
}
next we get to the strongly typed view where we will be using Display Templates:
<table id="myTable">
<thead>
<tr>
<th>DisplayUser</th>
<th>ActiveIndicationsUserFullname</th>
<th>Company</th>
<th>TimeOpened</th>
<th>TrxId</th>
</tr>
</thead>
<tbody>
<%= Html.DisplayForModel()
</tbody>
</table>
and in the corresponding display template (~/Views/Shared/DisplayTemplates/MyViewModel.ascx):
<%# Control
Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<AppName.Models.MyViewModel>" %>
<tr>
<td><%= Html.DisplayFor(x => x.DisplayUser) %></td>
<td><%= Html.DisplayFor(x => x.ActiveIndicationsUserFullname) %></td>
<td><%= Html.DisplayFor(x => x.Company) %></td>
<td><%= Html.DisplayFor(x => x.TimeOpened) %></td>
<td><%= Html.DisplayFor(x => x.TrxId) %></td>
<td>
<input type="button" value="Open" name="<%= Model.TrxId %>" />
</td>
</tr>
and finally you could use jquery to attach to the click of this button and fetch the name:
$(function() {
$('#myTable button').click(function() {
var btn = $(this);
alert(btn.attr('name'));
});
});