I have a partial view where I render, if the user has choosen an option, a button that permit the user to generate automatically a value for a certain field. Please give a look at this picture to understand what I mean:
This is achieved using the following markup on the partial view
<%= Html.LabelFor( model => model.IssueCode )%>
<br />
<% if ( Model.HasCodeGenerator ) { %>
<%= Html.TextBoxFor( model => model.IssueCode, new { style = "width:120px;background-color:#eeeeee;border: solid 2px #dfdfdf", #readonly = "readonly" } )%>
<% if (Model.ModelState == ModelStateEnum.Add) { %>
<button id="codeGenerator" style="font-size: 0.7em;margin-right: 10px">Genera codice fascicolo</button>
<% } %>
<% } else { %>
<%= Html.TextBoxFor(model => model.IssueCode, new { style="width: 120px" })%>
<% } %>
<%= Html.ValidationMessageFor(model => model.IssueCode, "*")%>
As you can see I append always an Html.ValidationMessageFor() at the end of the input field and a ValidationSummary aut the end of the view.
When the user submits the form the first block of code executed by the action is the following
if ( !ModelState.IsValid ) {
//Invalid - redisplay form with errors
return PartialView( "IssueCodeGenerator", model );
}
and this is the result I am getting in all of the three cases
Why the markup code for the button disappear?
Thanks for helping!
1st EDIT:
After validation the IssueCode textbox loose it's readonly="readonly" attribute. This means that the first condition is not meet, I think....
2nd EDIT:
As per the Darin comment I am including
The action that show the Partial View
An extract of the partial that show that the ModelState variable is kept as an hidden control inside the form
The controller Action called by the Partial
The jQuery code that submit the partial
1 - This is the action that shows the partial
[HttpGet]
public ActionResult Create()
{
IssueModel im = new IssueModel()
{
ModelState = ModelStateEnum.Add,
FirmID = _firmManager.GetMyFirmID(),
CreatedDate = DateTime.Now,
LastUpdateDate = DateTime.Now,
HasCodeGenerator = _optionManager.HasIssueCodeGenerator()
};
return PartialView("Issue", im);
}
2 - Extract of the partial Issue.ascx
<% using (Html.BeginForm("SaveOrDelete", "Issue", FormMethod.Post, new { id = "crudForm" })) { %>
<%= Html.HiddenFor(model => model.FirmID) %>
<%= Html.HiddenFor(model => model.IssueID) %>
<%= Html.HiddenFor(model => model.ModelState) %>
3 - This is the controller action called when the form is submitted
[HttpPost]
public ActionResult SaveOrDelete( IssueModel model ) {
if ( !ModelState.IsValid ) {
//Invalid - redisplay form with errors
return PartialView( "Issue", model );
}
try {
Issue i = null;
if ( model.ModelState == ModelStateEnum.Add )
i = new Issue();
else
i = _manager.FindIssueByIssueID( model.IssueID );
if ( model.ModelState != ModelStateEnum.Delete ) {
_manager.BindIssueModel( i, model );
if ( model.ModelState == ModelStateEnum.Add )
i.FirmID = _contactManager.GetMyContact().FirmID;
i.LastUpdateDate = DateTime.Now;
_manager.SaveIssue( i );
} else {
_manager.DeleteIssue( i );
}
return PartialView( "ActionCompleted" );
}
catch ( Exception ex ) {
return PartialView( "ActionError",
new ActionErrorModel() { Message = ex.Message } );
}
}
4 - This is the jQuery code that submit the form
$("#crudForm").submit(function(event) {
event.preventDefault();
$("#crudForm").block();
$.ajax({
type: "post",
dataType: "html",
url: "/Issue/SaveOrDelete",
sync: true,
data: $("#crudForm").serialize(),
success: function(response) {
$("#crudForm").parent().html('').html(response);
$("#crudForm").unblock();
},
error: function(response) {
$("#crudForm").unblock();
}
});
});
Hope that this would help in finding the problem. Thank you.
Because Model.ModelState == ModelStateEnum.Add evals to false?
MVC won't turn off your button html...
Even without seeing the form its pretty clear that Darin is right and your Modelstate enum is getting set to null.
Related
I have a problem with edit template data bind for kendo grid.
I have binded my grid to model which is DataTable(it must be DataTable because all cloumns must be generated dynamic - I do not know the schema), I added export, sortable, pages etc. I also added edit button, and defined another cshtml file with edit template. It all works almost perfect. One problem is that I receive the empty model for edit template.
View:
#(Html.Kendo().Grid(Model.Data)
.Name("OpertionalViewGrid")
.DataSource(dataSource => dataSource
.Ajax()
.ServerOperation(false)
.Events(events => events.Error("error_handler"))
.Model(model =>
{
model.Id(p => p.Row[0]);
if (Model.Data != null)
{
foreach (System.Data.DataColumn column in Model.Data.Columns)
{
var field = model.Field(column.ColumnName, column.DataType);
}
}
})
.PageSize(20)
.Read(read =>
read.Action("Export_Read","OperationalView").Data("getRow"))
.Update(up => up.Action("UpdateRow", "OperationalView").Data("getRow"))
)
.Columns(c =>
{
if (Model.Data != null)
{
int index = 0;
foreach (System.Data.DataColumn column in Model.Data.Columns)
{
var col = c.Bound(column.ColumnName).Width("300px");
if (index < 2)
{
col.Locked(true).Lockable(false);
}
index++;
}
c.Command(command => { command.Edit();}).Width(100);
}
})
.Editable(editable => editable
.Mode(GridEditMode.PopUp)
.TemplateName("OperationalViewEditor")
)
.Scrollable()
.Reorderable(reorder => reorder.Columns(true))
.Resizable(r => r.Columns(true))
.Filterable(ftb => ftb.Mode(GridFilterMode.Row))
.HtmlAttributes(new { style = "height:100%" })
.Pageable()
.Navigatable()
.Sortable()
.Groupable(g => g.ShowFooter(true))
.ToolBar(tools => tools.Excel())
.Excel(excel => excel.AllPages(true))
.ToolBar(tools => tools.Pdf())
.Pdf(pdf => pdf
.AllPages()
.AvoidLinks()
.PaperSize("A4")
.Scale(0.8)
.Margin("2cm", "1cm", "1cm", "1cm")
.Landscape()
.RepeatHeaders()
.TemplateId("page-template")
.FileName("Export.pdf")
)
)
I have also my getRow function:
<scriprt>
function getRow()
{
var grid = $('#OpertionalViewGrid').data('kendoGrid');
var selectedItem = grid.dataItem(grid.select());
return selectedItem;
} </script>
In my controler I have:
public ActionResult Export_Read([DataSourceRequest]DataSourceRequest request)
{
var ovs = new OperationalViewService();
return Json(ovs.Read()/*.ToDataSourceResult(request)*/);
}
public ActionResult UpdateRow([DataSourceRequest] DataSourceRequest dsRequest, DataRowView row)
{
var t = 5;
return Json(row/*ModelState.ToDataSourceResult()*/);
}
And my edit template:
#model System.Data.DataRowView
<h3>Customized edit template</h3>
#if(Model == null)
{
<label> null</label>
}
else
{
<label> not null!!!!!!</label><br />
#Html.Label(Model.DataView.Table.Columns.Count.ToString())<br />
#Html.Label(Model.ToString())<br />
#Html.Label(Model.Row.Table.Columns.Count.ToString())<br />
}
#for (int i = 0; i < Model.DataView.Table.Columns.Count; i++)
{
#Html.LabelFor(model => model.DataView.Table.Columns[i].ColumnName.ToString())
#Html.LabelFor(model => model.Row[i].ToString())
<br />
}
So.. when I click Edit button, I get a window with proper header, with note "01234 not null!! 0 DataRowView 0 "
So I conclude that model is of proper type, one problem is to pass my selected row.
I am very new to web and Kendo/telerik so my question: how to send my selected grid row to edit template?
Another question is: why when I click "edit" i do not get into UpdateRow action in my controler?
I have ommited the problem by using edit mode InCell - one important note is to specify the column type, as shown here: http://www.telerik.com/forums/inline-batch-crud-fails-with-datatable-model
I'm currently using this Wizard in my application.
In one of my steps, I need to add and remove items from a table. Add functions works fine. But I can't make the Delete function work. I've searched SO and other resources online, can't find a solution.
This is what I've so far:
Wizard Step 2 Table:
<table id="LibList" class="table table-responsive table-striped">
<thead>
<tr>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Types)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Step3.Liabilities[0].Teams)
</th>
<th>
<label>Delete</label>
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Step3.Liabilities) {
Html.RenderPartial("_LibListItem", item);
}
</tbody>
</table>
Partial View:
<tr>
<td>
#Html.DropDownListFor(model => model.Type, Model.Types, new { #class = "selectpicker form-control", id = string.Format("{0}_{1}", "TYPE", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Name, new { #class = "form-control", id = string.Format("{0}_{1}", "NAME", Model.ID) })
</td>
<td>
#Html.TextBoxFor(model => model.Teams, new { #class = "form-control", #type = "currency", id = string.Format("{0}_{1}", "TERMS", Model.ID) })
</td>
<td>
#Ajax.ActionLink("Delete", "Delete", "QuestionWizard", new { id = Model.ID },
new AjaxOptions() {
HttpMethod = "Delete",
Confirm = "Are you sure you want to delete this record?",
OnComplete = "function() { $(this).parent().parent().remove() }"
},
new { #class = "btn btn-primary" })
</td>
Add and Delete Action:
public ActionResult AddRow() {
LiabilityModel lib = new LiabilityModel();
try {
if (LiabilitySessionState.Count == 0) {
lib.ID = 1;
} else {
lib.ID = LiabilitySessionState.LastOrDefault().ID + 1;
}
LiabilitySessionState.Add(lib);
return PartialView("_LibListItem", lib);
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
return RedirectToAction("Index", "Error");
}
}
public void Delete(int Id) {
try {
if (LiabilitySessionState.Count > 0) {
LiabilitySessionState.RemoveAll(item => item.ID == Id);
}
} catch (Exception ex) {
System.Web.HttpContext.Current.Application.Add("LASTERROR", ex);
RedirectToAction("Index", "Error");
}
}
public List<LiabilityModel> LiabilitySessionState {
get {
if (Session["LiabilityModel"] == null) {
return LiabilitySessionState = new List<LiabilityModel>();
} else {
return Session["LiabilityModel"] as List<LiabilityModel>;
}
}
set {
Session["LiabilityModel"] = value;
}
}
Script:
<script type="text/javascript">
$(document).ready(function () {
$('.add-button').click(function () {
var action = "/QuestionWizard/AddRow";
$.ajax({
url: action,
cache: false,
success: function (result) {
$("#LibList tbody").append($(result));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
}
});
})
$(".remove-button").click(function () {
$(this).parents().parent().remove();
return false;
});
});
Right now, the Delete Action returns a blank page, because I'm not returning a view. But, I'm not sure what view to return.
Please help!
Thanks in advance.
Firstly your Delete() method is changing data so it needs to be a POST, not a GET (which #Ajax.ActionLink() is). Next, your 'delete link' does not have class="remove-button" so $(".remove-button").click(function () { wont do anything. Since you already using jquery ajax methods to add items (refer notes below), there seems no point using the Ajax helpers anyway (your just adding extra overhead by loading the jquery.unobtrusive-ajax.js file).
Change the last <td> element of the partial to
<td>
<button type="button" class="remove-button" data-id=#Model.ID>Delete</button>
</td>
Then in the main view, use the following script (note you need event delegation so you can remove dynamically added items)
var url = '#Url.Action("Delete", "YourControllerName")';
$('#LibList').on('click', .remove-button, function() {
var row = $(this).closest('tr');
var id = $(this).data('ID');
if (id) { // assumes property ID is nullable, otherwise may need to test if id != 0
$.post(url, {ID: id }, function(response) {
if(response) {
row.remove();
} else {
// Oops - display message?
}
});
} else {
// its a new row so it has not been saved yet - just delete the row
row.remove();
}
});
and the controller method should look like
[HttpPost]
public JsonResult Delete(int ID)
{
// delete the item
return Json(true); // or if an exception occurred, return(null) to indicate failure
}
Side notes:
Both your AddRow() and Delete() methods include a return
RedirectToAction() statement. You are making ajax calls which stay
on the same page so RedirectToAction() is pointless (it will never
be executed)
If you believe the "Add functions works fine", then you must have
some unnecessary hacks in order to make this work (your generating
duplicate name attributes for the controls (without indexers)
which will not bind to your collection of Liabilities). You need
to generate existing items in a for loop and include a hidden
input for an Index property, and use a client side template to
generate new items; or you need to use the BeginCollectionItem
helper if using a partial view. This
answer
shows how to use both techniques.
You don't appear (and you should not need) to access the id
attributes of the controls in your table. Instead of using id = string.Format("{0}_{1}", "NAME", Model.ID), just use id="" which
will render the element without and id attribute.
I have tried the many solutions online for trying to get validation to work when loading a partial view, but none of them seemed to work.
I currently have some jQuery code which looks like:
$(".alert").click(function () {
$("#add-comics-container").load("/ManageComics/ComicEditor", function () {
$.validator.unobtrusive.parse("#add-comics-container");
});
$("#add-comics-container").fadeIn();
$(".blackout").css("display", "block");
return false;
});
My /ManageComics/ComicEditor looks like:
public ActionResult ComicEditor() {
return PartialView("_ComicEditorPartial");
}
My Partial more or less looks like:
#model Comics.Models.LocalComicModel
#Html.BeginForm("Index", "ManageComics", FormMethod.Post, new { enctype = "multipart/form-data", id = "addComicForm", value = "1"}){
<div class="add-comics-item">
<div class="add-comics-left">
<span class="add-comics-title bold_text">#Html.LabelFor(u => u.Title)</span>
<div class="add-comics-help">WebComic Title throughout website</div>
#Html.ValidationMessageFor(u => u.Title)
</div>
<div class="add-comics-right">
#Html.TextBoxFor(u => u.Title)
</div>
</div>
}
I am working on a security metrix page similar to the below:-
currently i have the following code inside my view:-
#foreach(var item in Model.PermisionLevel.OrderByDescending(a=>a.PermisionSize)){
<th>
</th>}
</tr>
#{
int i =0 ;
foreach (var item2 in Model.TechnologyType.OrderBy(a=>a.Name)) {
<tr>
<td class="f">
#item2.Name
</td>
#foreach (var item3 in Model.PermisionLevel.OrderByDescending(a=>a.PermisionSize))
{
<td class="f">
#Html.RadioButton(item2.Name, item3.PermisionLevelID)
#Html.Hidden("AssetTypeID" , item2.AssetTypeID)
#Html.Hidden("PermisionLevelID",item3.PermisionLevelID)
#Html.Hidden("SecurityRoleID",Model.SecurityRole.SecurityRoleID)
</td>
}
</tr>
}
}
and the following ActionMethod:-
[HttpPost]
public ActionResult AssignPermisionLevel(ICollection<SecurityroleTypePermision> list)
{
foreach (var c in list)
{
repository.InsertOrUpdateSecurityroleTypePermisions(c);
}
repository.Save();
return RedirectToAction("Index");
}
But i am not sure about how i can pass the associated hidden field values only if the related radio button was checked. currently if i submit the view , the action method will raise a null exception?
Can anyone advice on how to fix this ?
each radio group has a name, which looks like your item2.Name.
// see if this group is selected
var radWebSite = $('[name="SomeName"]:checked');
if (radWebSite.length > 0) {
// this group has a selection, get the value
var WebSiteVal = radWebSite.val();
}
if you get all of your answers into variables or some object, you can pass that down to your action method, here is a suggestion
$('#SaveClick').click(function() {
var SavePermissions = {};
SavePermissions.WebSite = WebSiteVal // this is the variable from above
// .... do this for each radio group
var DTO = { 'DTO': SavePermissions };
$.ajax({
type: "POST",
contentType: 'application/json;charset=utf-8',
url: "/ControllerName/ActionMethodName",
dataType: "json",
data: JSON.stringify(DTO),
success: function (data) {
// do success stuff
},
error: function(data){
// do error stuff
},
complete: function () {
//do final stuff
}
});
});
Then in your action method create a class that has a property for each item you put in SavePermissions on the javascript, probably should even call it SavePermissions
public ActionResult AssignPermisionLevel(SavePermissions DTO){
// save stuff
}
*Edit: *
I didn't think about this before, but we have a reference to Newtonsoft.Json in our application, you'll probably need that to do this...
I have a partial view that exists multiple times on the same page. This is added to the view :
#Html.Partial("PersonalTagPartial", Model.PT, new ViewDataDictionary{{"TagTypeControl", 1}} )
#Html.Partial("PersonalTagPartial", Model.PT, new ViewDataDictionary{{"TagTypeControl", 0}} )
Then in the partialview I got a submit form like this :
#using (Html.BeginForm("AddPersonalTag", "Post", FormMethod.Post, new { id = "frmAddPersonalTags" })){
How do I change the AddPersonalTag to RemovePersonalTag when TagTypeControl is set to 0?
Try like this:
#using (Html.BeginForm((ViewBag.TagTypeControl == "1" ? "AddPersonalTag" : "RemovePersonalTag"), "Post", FormMethod.Post, new { id = "frmAddPersonalTags" }))
{
...
}
but you could also directly pass the action name to the partial:
#Html.Partial("PersonalTagPartial", Model.PT, new ViewDataDictionary { { "SubmitTo", "AddPersonalTag" } })
#Html.Partial("PersonalTagPartial", Model.PT, new ViewDataDictionary { { "SubmitTo", "RemovePersonalTag" } })
and then inside the partial:
#using (Html.BeginForm(ViewBag.SubmitTo, "Post", FormMethod.Post, new { id = "frmAddPersonalTags" }))
{
...
}