I trying to create ImageBrowser in Asp.net MVC 5. Problem is when I try to switch to another picture.
Here's my code:
In View:
#model Katalog.Models.Model
#{
Model.enumerator = 0;
int count = Model.ImageList.Count;
int index = 1;
}
....
<table>
<tbody>
<tr>
<td> #index/#count </td>
....
</tr>
<tr>
#using (Html.BeginForm("previous", "Home",FormMethod.Post))
{
<td>
<input type="hidden" name="number" value="1" />
<input value="<" type="submit" onclick="ImageLeft()" class="buttonLeftRight"/>
</td>
}
<td>#{Html.RenderPartial("~/Views/Shared/ImageViews.cshtml", Model);}</td>
<td>
#using (Html.BeginForm("next", "Home", FormMethod.Post))
{
#Html.HiddenFor(a => a.ImageList)
#Html.HiddenFor(a => a.enumerator)
<input type="submit" class="buttonLeftRight" onclick="ImageRight()"/>
}
</td>
</tr>
</tbody>
</table>
....
<script>
function ImageRight()
{
#{ Model.enumerator++; }
}
</script>
My Controller
....
public ActionResult next(Katalog.Models.Model model)
{
model = MyModel;
return View("Searcher",model);
}
....
and my PartialView:
#model Katalog.Models.Model
<img id="foto" class="imgFinded" src="#Model.ImageList[#Model.enumerator]"/>
When I click Next button my model.ImageList is empty. Why?
The reason your models ImageList property is null is because your generating a hidden input #Html.HiddenFor(a => a.ImageList) which generates
<input name="ImageList" value="System.Collections.Generic.List[]String" .. />
which will not bind to your collection in the POST method (when something is not binding, always insect the name and value attribute of the form controls your generating.
In order to bind to that list you would need to generate an input for each item in the list using a loop.
The other issue is that your scripts does nothing at all. Your model is server side code, and you cannot increment the value of a model property using javascript - you need to send a request to the controller to do that.
Since you sending the collection of images to the view, there is no need to make a request back to the server - you can just update the src attribute of the <img> tag.
Change the model to
public class Model
{
public int InitialIndex { get; set; }
public int ImageCount { get { return ImageList.Count; } }
public List<string> ImageList { get; set; }
}
and then the view can be just (the partial is unnecessary)
<img id="foto" class="imgFinded" src="#Model.ImageList[#Model.InitialIndex ]"/>
<button type="button" id="previous">Previous</button>
<button type="button" id="next">Next</button>
and the scripts
var imagelist = #Html.Raw(Json.Encode(Model.ImageList));
var index = #Html.Raw(Json.Encode(Model.InitialIndex));
var max = #Html.Raw(Json.Encode(Model.ImageCount));
var image = $('#foto');
// Set the initial state of the buttons
if (index === 0) {
$('#previous').prop('disabled', true);
} else if (index === max) {
$('#previous').prop('disabled', true);
}
$('#next').click(function() {
$('#previous').prop('disabled', false);
index++;
image.attr('src', imagelist[index]);
if (index === max) {
$(this).prop('disabled', true);
}
})
$('#previous').click(function() {
$('#next').prop('disabled', false);
index--;
image.attr('src', imagelist[index]);
if (index === 0) {
$(this).prop('disabled', true);
}
})
The code #{Model.enumerator++;} and #{Model.enumerator--;} within the two Javascript functions is server side code so it will only be executed once when the view is rendering on the server and before it is passed to the client browser. So triggering the onclick="ImageRight()" by pressing the submit button will not change the server side value.
You could instead try to post the current index to the action in your controller and increment or decrement it depending on which action has been invoked.
#using (Html.BeginForm("Next", "Home", FormMethod.Post))
{
#Html.HiddenFor(a=>a.CurrentIndex)
<input type="submit" class="buttonRightLeft"/>
}
#using (Html.BeginForm("Previous", "Home", FormMethod.Post))
{
#Html.HiddenFor(a=>a.CurrentIndex)
<input type="submit" class="buttonLeftRight"/>
}
public ActionResult Next(int CurrentIndex)
{
// Get the NEXT image and return as model
model = MyModel;
return View("Searcher",model);
}
public ActionResult Previous(int CurrentIndex)
{
// Get the PREVIOUS image and return as model
model = MyModel;
return View("Searcher",model);
}
Related
I want to show an info modal that says "Record successfully deleted." after clicking the button inside a Confirmation Modal.
Here is my code to show the confirmation modal
Controller
public IActionResult Delete()
{
return PartialView("_ModalDelete");
}
_ModalDelete.cshtml
#using Data.ViewModels.Modal
#using (Html.BeginForm())
{
#await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = "Delete" })
<div class="modal-body form-horizontal">
Are you sure you want to delete this record?
</div>
#await Html.PartialAsync("_ModalFooter", new ModalFooter { SubmitButtonText = "Delete" })
}
Example Screenshot:
This seems to be okay on this part. No issues encounter. But after clicking the Delete button, it will show my modal like a whole view. See below:
Here is my code:
Controller - for post of data after clicking delete button
[HttpPost]
public async Task<IActionResult> Delete(int id)
{
try
{
var validationResult = await new RegionHandler(_regionService).CanDelete(id);
if (validationResult == null)
{
await _regionService.DeleteById(id);
return PartialView("_ModalInfo", new Tuple<string, string>(Constants.Message.Info, Constants.Message.RecordSuccessDelete));
}
ModelState.AddModelError(validationResult);
}
catch (Exception ex)
{
var exceptionMessage = await Helpers.GetErrors(ex, _emailService);
ModelState.AddModelError(new ValidationResult(exceptionMessage));
}
ModelState.AddModelError(string.Empty, "Invalid delete attempt.");
return PartialView("_ModalInfo", new Tuple<string, string>(Constants.Message.Error, ModelState.ToString()));
}
_ModalInfo.cshtml
#using Data.ViewModels.Modal
#model Tuple<string,string>
#await Html.PartialAsync("_ModalHeader", new ModalHeader { Heading = Model.Item1})
<div class="modal-body form-horizontal">
#Model.Item2
</div>
#await Html.PartialAsync("_ModalFooter", new ModalFooter { CancelButtonText = "OK", OnlyCancelButton = true})
With the submission of your form you are making a roundtrip to the server, which will issue a completely new html page (even if your html code is only partial).
To remove the question-modal and replace it with a message-modal in the original page (region-list), you will have to use javascript (for the post AND the replacement).
If you want to stick with the roundtrip, make the Delete method return a full html page, which integrates the message-dialog (like the region-list intergrates the question-dialog).
Finally found an answer with this. So basically I just revised everything so that the model validation from controller will still be there.
Heres my code:
For the table markup
<tr>
<td>
#Html.DisplayFor(modelItem => item.RegionName)
</td>
<td>
#Html.DisplayFor(modelItem => item.RegionCode)
</td>
<td>
#Html.DisplayFor(modelItem => item.RegionKey)
</td>
<td>
#Html.DisplayFor(modelItem => item.Description)
</td>
<td class="text-center">
<a asp-action="Edit" asp-route-id="#item.RegionId"><i class="fa fa-edit text-info"></i></a>
<i class="fa fa-trash text-danger"></i>
</td>
</tr>
where it call a javascript function below:
#section Scripts{
<script type="text/javascript">
function showDeleteConfirmation(message, event, id) {
event.preventDefault();
showConfirmationModal(message).then(function () {
$("#id").val(id);
$("#formDelete").submit();
});
}
</script>
}
where showConfirmationModal() is a promise function that uses bootbox.js (library that wraps bootstrap modal for easier usage).
site.js
function showConfirmationModal(message, title = "Confirm", size = "medium", confirmText = "Yes", canceltext = "No") {
const deffered = $.Deferred();
bootbox.confirm({
title: title,
message: message,
size: size,
buttons: {
confirm: {
label: confirmText,
className: "btn-success"
},
cancel: {
label: canceltext,
className: "btn-danger"
}
},
callback: function (result) {
if (result) {
deffered.resolve(result);
} else {
deffered.reject(result);
}
}
});
return deffered.promise();
}
On callback, it will submit the hidden form below. Ofcourse don't forget to set the id to be deleted.
Hidden form for Delete action
<form method="post" asp-action="Delete" id="formDelete" class="hidden">
<input type="hidden" id="id" name="id" />
<input type="hidden" asp-for="Item1.RegionName" name="RegionName" />
<input type="hidden" asp-for="Item1.Page" name="Page" />
<input type="hidden" asp-for="Item1.SortBy" name="SortBy" />
<input type="hidden" asp-for="Item1.SortOrder" name="SortOrder" />
</form>
To show the Info Message(for success delete), I created a PartialView to make the modal show if theres a data set in a Temporary Data or TempData. This was added under the _Layout.cshtml page:
_ModalScriptsInit.cshtml
#using Data.Utilities
#{
var text = TempData[Constants.Common.ModalMessage];
if (text != null && !text.Equals(string.Empty))
{
<script type="text/javascript">
showDefaultModal("#text");
</script>
}
}
So in my controller once successfully delete I will just set the TempData with its key as shown below:
Controller
[HttpPost]
public async Task<IActionResult> Delete(int id, RegionSearchViewModel searchViewModel)
{
try
{
var validationResult = await new RegionHandler(_regionService).CanDelete(id);
if (validationResult == null)
{
await _regionService.DeleteById(id);
TempData[Constants.Common.ModalMessage] = Constants.Message.RecordSuccessDelete;
return RedirectToAction(nameof(List), searchViewModel);
}
ModelState.AddModelError(validationResult);
}
catch (Exception ex)
{
var exceptionMessage = await Helpers.GetErrors(ex, _emailService);
ModelState.AddModelError(new ValidationResult(exceptionMessage));
}
ModelState.AddModelError(string.Empty, "Invalid delete attempt.");
return RedirectToAction(nameof(List), searchViewModel);
}
I am not sure yet if this is the best way so far. Please give suggestion on how to improve this code. Thanks!
I know this is a question that many people responded on the site , but no solution seems to work to my problem.
I am new to MVC and do not know how to send the selected item in the drop down list to the controller .
public class MonthDropDownList
{
public IEnumerable<SelectListItem> Months
{
get
{
return DateTimeFormatInfo
.InvariantInfo
.MonthNames
.Where(m => !String.IsNullOrEmpty(m) )
.Select((monthName, index) => new SelectListItem
{
Value = (index + 1).ToString(),
Text = monthName
});
}
}
public int SelectedMonth { get; set; }
}
Here is my view :
#model Plotting.Models.MonthDropDownList
#Html.DropDownListFor(x => x.SelectedMonth, Model.Months)
#using (Html.BeginForm("MonthlyReports", "Greenhouse", FormMethod.Post))
{
<input type="submit" name="btnSubmit" value="Monthly Report" />
}
And here is the ActionResult in which i should use the selected date :
public ActionResult MonthlyReports(MonthDropDownList Month)
{
Debug.Write("Month" + Month.SelectedMonth);// <- always = 0
InitChartModel();
cDate.DateTitle = "Day";
string msg = dal.Connection("month");
List<Greenhouse> greenhouse = dal.FindIfDMY("month" , Month.SelectedMonth , msg);
cDate.DateData = GetChart(greenhouse, "month");
return View("MonthlyReports", cDate);
}
You should move your DropDownList into your form.
#model Plotting.Models.MonthDropDownList
#using (Html.BeginForm("MonthlyReports", "Greenhouse", FormMethod.Post))
{
#Html.DropDownListFor(x => x.SelectedMonth, Model.Months)
<input type="submit" name="btnSubmit" value="Monthly Report" />
}
Your form control needs to be inside the form tags
#using (Html.BeginForm("MonthlyReports", "Greenhouse", FormMethod.Post))
{
#Html.DropDownListFor(x => x.SelectedMonth, Model.Months) // move here
<input type="submit" name="btnSubmit" value="Monthly Report" />
}
I'm trying to build an inbox that is very similar to facebooks message inbox, where you have a list of conversations(I only want a list of a message title) and when you click the conversation or message title in my situation, I want the whole message to be rendered next to it in a partial view.
Here's my Inbox view:
#model BlocketProject.Models.ViewModels.ProfilePageViewModel
#{
ViewBag.Title = "Inbox";
}
<h2>Dina meddelanden:</h2><br />
<div class="left">
<table id="messageTable">
#foreach (var message in Model.UserMessages)
{
<tr>
<td>
<button type="submit" class="messageButton">
#if (message.Unread == true)
{
<h4 style="font-weight:bold;">#message.MessageTitle</h4>
}
else if (message.Unread == false)
{
<h4>#message.MessageTitle</h4>
}
</button>
</td>
</tr>
}
</table>
</div>
<div class="right">
#Html.Partial("ReadMessage")
</div>
When I click this message-element that is a button, I want to pass that messageId to the PartialView ReadMessage:
#model BlocketProject.Models.DbClasses.DbMessages
<h2>#Model.MessageTitle</h2><br />
<p>#Model.MessageText</p>
and the controller looks like this:
[HttpPost]
public ActionResult Inbox()
{
var allMessages = ConnectionHelper.GetAllMessages(ConnectionHelper.GetUserByEmail(User.Identity.Name).UserId);
var model = new ProfilePageViewModel();
model.UserMessages = allMessages;
return View("Inbox", model);
}
[HttpPost]
public ActionResult ReadMessage(int messageId)
{
var model = ConnectionHelper.GetMessageByMessageId(messageId);
return PartialView("ReadMessage", model);
}
I've tried passing the messageId through a post as you can see in my controller, but then the partialView is returned as a new page and I simply want to render it in the Inbox view.
Any ideas?
EDIT:
Jonesy's answer fixed my problem when I edited it like this:
Controller:
public ActionResult ReadMessage(int messageId)
{
var model = ConnectionHelper.GetMessageByMessageId(messageId);
return PartialView("ReadMessage", model);
}
View:
<div class="left">
<table id="messageTable">
#foreach (var message in Model.UserMessages)
{
<tr>
<td>
#using (Ajax.BeginForm("ReadMessage", new { #messageId = message.MessageId }, new AjaxOptions { UpdateTargetId = "showMessage" }, FormMethod.Post))
{
<button type="submit" class="messageButton">
#if (message.Unread == true)
{
<h4 style="font-weight:bold;">#message.MessageTitle</h4>
}
else if (message.Unread == false)
{
<h4>#message.MessageTitle</h4>
}
</button>
}
</td>
</tr>
}
</table>
</div>
<div class="right" id="showMessage">
#Html.Partial("ReadMessage", new BlocketProject.Models.DbClasses.DbMessages())
</div>
Razor is run on the server, before the page is rendered. Once the page is on the client, and they can click a message, the concept of the PartialView is gone - it's all just one HTML page.
The easiest way for you to do this is to use an Ajax.BeginForm where your button is, and on click, update an element with a partial view retrieved from the server. Something like:
#using(Ajax.BeginForm("ReadMessage", "Messages", new AjaxOptions() { UpdateTargetId = "showMessage" })) {
//...
}
//...
<div class="right" id="showMessage">
//ReadMessage partial rendered on button click
</div>
A little method that could help you :
protected ActionResult View(string viewName, object model)
{
if (ControllerContext.IsChildAction)
return PartialView(viewName, model);
else if (Request.IsAjaxRequest())
return PartialView(viewName, model);
else
return View(viewName, model);
}
This will return a PartialView when you call you action via #Html.RenderAction() or call the action via Ajax (jQuery).
You can then use jQuery to prevent the form to be posted and post it with Ajax or when you click on a message, you can also use jQuery to get the result from the action and insert it in your DOM.
I am pretty new in C# and .NET MVC framework and I have the following problem.
I have a first JQuery Mobile view that show a navbar containing some tabs. Into one of this tab I putted a ListView that show the element of a collection of DataModel.Vulnerability.Fix objects represented by the Model.VulnerabilityFixes into my model object. On the right of every element of the list I have put a button/link to delete the related Fix object that generate this row in the list.
This work fine and I obtain the following result (I post a screenshot):
This is the code of the previous tab (the one that show the Fix list):
<!-- TAB-2: FIXES, SOLUTION e MITIGATING STRATEGY: -->
<div id="tab-2" class="ui-body-d ui-content">
<h3>Fixes</h3>
<a href="#Url.Action("SearchCPE", "Asset", new { id = Model.Id })" data-icon="plus" data-inline="true" data-mini="true" data-role="button" >Aggungi un Fix</a>
<!-- Tabella contenente la lista delle fix: -->
<ul data-role="listview" data-inset="true" data-theme="b" data-split-icon="delete">
#foreach (DataModel.Vulnerability.Fix item in Model.VulnerabilityFixes)
{
<li><a href="#Url.Action("Details", "Product", new { Title = item.Title })">
<h2>#item.Title</h2>
<table style="width: 100%">
<tr>
<th>Id</th>
<th>FixName</th>
<th>Vendor</th>
<th>Version</th>
</tr>
<tr>
<td>#MyHelper.decodeNull(item.Id)</td>
<td>#MyHelper.decodeNull(item.FixName)</td>
<td>#MyHelper.decodeNull(item.Vendor)</td>
<td>#MyHelper.decodeNull(item.Version)</td>
</tr>
</table>
</a>
Delete
</li>
}
</ul>
</div>
<!-- /tab-2 -->
Ok, now I have a DeleteFix() method into the EditingController class that handle the request generated clicking od the Delete button.
This one:
public ActionResult DeleteFix(long vulnId, int currentFixId, string currentFixName)
{
DataModel.Vulnerability.Fix model = new DataModel.Vulnerability.Fix();
manager.openConnection();
try
{
model.Id = currentFixId;
model.FixName = currentFixName;
}
finally
{
manager.closeConnection();
}
return View(model);
}
This method show another view, the DeleteFix.cshtml file, that show a confirm window where is asked to the user to confirm the delete operation or if come back to the previous page, this is the code:
#model DataModel.Vulnerability.Fix
#{
ViewBag.Title = "DeleteFix";
Layout = "~/Views/Shared/MasterPageMobile.cshtml";
}
<h1>Delete Fix</h1>
<h2>Fix: #Model.Title (id: #Model.Id)</h2>
<p>
Confermare la cancellazione del fix "#Model.FixName" ?
</p>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<input type="hidden" name="id" value ="#Model.Id" />
<div data-role="controlgrup" data-type="horizontal" data-mini="true">
<a href="#Url.Action("Index", "Editing", new { id = Model.Id })" data-inline="true" data-mini="true" data-role="button" >Torna alla lista</a>
<a href="#Url.Action("Details", "Groups", new { id = Model.Id })" data-mini="true" data-inline="true" data-role="button" >Annulla</a>
<input type="submit" value="Delete" data-mini="true" data-inline="true" />
</div>
}
My problem is that I want that if the user click on the Torna alla lista button it have to be taken to the initial view that show the list of Fix object but I can't do it
Someone can help me to understand what am I missing? What can I do to obtain this result?
Tnx
Basically I would add a new property in the viewmodels used in the secondary views to have a trace of the primary url :
public string BackUrl { get; set; }
Maybe if you want this feature for more that I secondary view, you can create a base view model that all secondary viewmodels inherit.
Then, when calling a secondary view, just initialize the BackUrl property :
Delete
In the end, in the DeleteFix action, redirect to the BackUrl instead of redering the view (if the viewstate is valid :
public ActionResult DeleteFix(long vulnId, int currentFixId, string currentFixName)
{
if (ModelState.IsValid)
{
DataModel.Vulnerability.Fix model = new DataModel.Vulnerability.Fix();
manager.openConnection();
try
{
model.Id = currentFixId;
model.FixName = currentFixName;
}
finally
{
manager.closeConnection();
}
return Redirect(viewModel.BackUrl);
}
// Invalid viewstate, re-render the view
return View(model);
}
I am learning MVC by converting an old Web Form to MVC 5. The page is pretty simple, just a few text boxes, a submit button and a grid. I want similar functionality where there is no grid on page load, and after the submit takes place, the grid appears. The issue I am having is all the examples I have seen contain the data coming at page load.
Here is my View
<h2>Search Craigslist</h2>
#using (Html.BeginForm())
{
<p>
Enter Search Criteria:<br />
#Html.TextBox("Criteria", null, new { #style = "width: 450" })
#Html.DropDownList("DropDownValues")
Days:
#Html.TextBox("Days")
<br />
<input type="submit" value="Submit" />
</p>
}
#Html.Partial("~/Views/Search/_Search.cshtml")
and the partial
#model IEnumerable<CraigsListSearch.MVC.Models.SearchModel>
#{
Layout = null;
}
<table cellspacing="0" border="1" style="border-collapse:collapse;">
<tr>
<th>
#Html.DisplayNameFor(model => model.Location)
</th>
<th>
Link
</th>
<th>
#Html.DisplayNameFor(model => model.DateSubmitted)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Location)
</td>
<td>
#Html.Hyperlink(Html.DisplayFor(modelItem => item.URL).ToString(), Html.DisplayFor(modelItem => item.Title).ToString())
</td>
<td>
#Html.DisplayFor(modelItem => item.DateSubmitted)
</td>
</tr>
}
and finally the controller
public class SearchController : Controller
{
[HttpGet]
//public ViewResult Index(string DropDownValues, string Criteria, string Days)
public ViewResult Index()
{
Populate();
Session["list"] = SearchModel.GetQuickList();
return View(new List<SearchModel>());
}
[HttpPost]
public ViewResult Index(string DropDownValues, string Criteria, string Days)
{
Populate();
Criteria = Server.UrlPathEncode(Criteria).Replace(",", "%2c");
List<Location> list = (List<Location>)(Session["list"]);
List<SearchModel> results = SearchModel.Search(Criteria, Days, DropDownValues, list.Take(5).ToList());
return View(results);
}
private void Populate()
{
List<DropDownModel> ddlList = DropDownModel.GetDropdowns();
ViewBag.DropDownValues = (from a in ddlList
select new SelectListItem
{
Text = a.Text,
Value = a.Value
}).ToList();
}
}
What I am looking for is to have the partial only be "called" on POST. Is this something that can easily be done? Let me know if other info is needed.
Thanks!
There are a couple of ways you could approach solving this problem.
One option would be to use AJAX. When the user clicks the button make an AJAX call to the controller and have the controller render the partial and return the HTML which you then inject into the page DOM.
Another option is to add a "DisplayGrid" property to your model. When you first render the View set it to false and then set it to true after the post.
One point I want to make is that rendering a View from the Post action isn't a great idea. If the user hits Refresh in their browser it's going to post again, giving the user that annoyingly ugly dialog that tells them they're going to send data to the server. In general you should use a Post-Redirect-Get pattern, the user posts, you redirect, and then you use a get to re-render in the browser. If you use the AJAX approach this all becomes moot because you never post the whole form back to a controller action. If you continue to use a full post the HttpPost version of Index should return a RedirectToAction result sending the user back to the Get version of Index. You can communicate between the two actions by using TempData, for example:
[HttpGet]
public ActionResult Index()
{
if( TempData.ContainsKey( "DisplayGrid" )
{
// Use the other values from TempData to populate the model with the grid data
myModel.DisplayGrid = (bool)TempData["DisplayGrid"];
}
}
[HttpPost]
public ActionResult Index( string dropDownValues, string criteria, string days )
{
TempData["DisplayGrid"] = true;
TempData["DropDownValues"] = dropDownValues;
TempData["Criteria"] = criteria;
TempData["Days"] = days;
return RedirectToAction( "Index" );
}
You should pass whatever information you need to render view via model (or ViewBag).
Since you already have sample of how to use Model here is how you can use ViewBag
#if(ViewBag.RenderSearch)
{
#Html.Partial("~/Views/Search/_Search.cshtml")
}
And set RenderSearch in particular action:
public ViewResult Index(string DropDownValues...
{
....
ViewBag.RenderSearch = true;
With model just add property to your model (you'd need to make it custom class instead of just List) and check that flag instead.
Try This, the Ajax only replace the GridDiv only
<h2>Search Craigslist</h2>
using (Ajax.BeginForm("BindGridAction", "Contoller",new AjaxOptions { UpdateTargetId = "GridDiv" }))
{
<p>
Enter Search Criteria:<br />
#Html.TextBox("Criteria", null, new { #style = "width: 450" })
#Html.DropDownList("DropDownValues")
Days:
#Html.TextBox("Days")
<br />
<input type="submit" value="Submit" />
</p>
}
<div id="GridDiv">
#Html.Partial("~/Views/Search/_Search.cshtml")
</div>
Bind your partial view in controller
public PartialViewResult BindGridAction()
{
// your model coding
return PartialView("~/Views/Search/_Search.cshtml", model);
}