Okay I'm totally new to this so I'm not going to get a lot of the terminology right, but it's an ASP.NET MVC application where a create view is supposed to have an autocomplete function on the "StudentID" text box. It doesn't work. On the console window I get an error message "Autocomplete is not a function". This is strange because the intellisense at the beginning does give me autocomplete. I don't know what I'm supposed to paste below but I put in the javascript as well as the applicable Controller code. LMK if there's anything else you need to see. Thanks in advance.
P.S. If you know a better way to do autocomplete (this is done through a class that we got the code from some website and looks more complicated than similar things I did in the past) please let me know that too.
<script>
$("#Student_FirstName").autocomplete(
{
source: function (request, response)
{
$.ajax(
{
url: "/Enrollments/GetStudents",
dataType: "json",
type: "POST",
data:
{
term: request.searchTerm
},
success: function (data) {
console.log(data);
response($.map(data, function (item) {
return {
label: item.Name, value: item.Name, id: item.id
};
}))
}
});
},
minLength: 2,
select: function (event, query)
{
console.log(query);
$("#StudentID").val(query.item.id);
}
});
</script>
And now from later on in the Create view:
<div class="form-group">
#Html.LabelFor(model => model.StudentID, "StudentID", htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Student.FirstName, new { htmlAttributes = new {#class = "form-control"}})
#Html.HiddenFor(model => model.StudentID)
#Html.ValidationMessageFor(model => model.StudentID, "", new { #class = "text-danger" })
</div>
</div>
And finally from the Controller Class:
// GET: Enrollments/Create
public ActionResult Create()
{
ViewBag.CourseID = new SelectList(db.Courses, "CourseID", "Title");
ViewBag.StudentID = new SelectList(db.Students, "StudentID", "LastName");
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FirstName");
return View();
}
// POST: Enrollments/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see https://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EnrollmentID,Grade,CourseID,StudentID,InstructorID")] Enrollment enrollment)
{
if (ModelState.IsValid)
{
db.Enrollments.Add(enrollment);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CourseID = new SelectList(db.Courses, "CourseID", "Title", enrollment.CourseID);
ViewBag.StudentID = new SelectList(db.Students, "StudentID", "LastName", enrollment.StudentID);
ViewBag.InstructorID = new SelectList(db.Instructors, "ID", "FirstName", enrollment.InstructorID);
return View(enrollment);
}
Related
I know that this question might have been already on this site, but there are some different things in my approach because I use #Html.EditFor and #Html.DropDownList.
So I have a drop down list and when I choose the ID there I want to retrieve some information from the DB then populate some fields in the current form. I know that I should use JS but I don't know how.
View:
#model TestApp.Dtos.InsertDto
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="col-md-9">
#Html.DropDownListFor(model => model.Category, new SelectList(Model.ListOfCategory, "Text", "Text"), "-- Please select --", htmlAttributes: new { #class = "form-control"});
</div>
<div class="col-md-9">
#Html.EditFor(model => model.Car, new{htmlAttributes = new { #class = "form-control" }});
</div>
#*And then the form continues with other fields and after that the submit button *#
}
You can use ajax to get data from backend,and put the result data into somewhere you want.Here is a simple demo to get a selectListItem from backend and put it into a div.If you want to do something more complex,you need to share the structure of InsertDto,and explain clearly what kind of data you will get from db and explain what does populate some fields in the current form mean?
View:
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="col-md-9">
#Html.DropDownListFor(model => model.Category, new SelectList(Model.ListOfCategory, "Text", "Text"), "-- Please select --", htmlAttributes: new { #class = "form-control" ,#onchange = "getData()" })
</div>
<div id="dbdata">
</div>
}
js:
<script>
function getData() {
$.ajax({
type: "POST",
data: { Category: $("#Category").val() },
url: '/B/GetData',
}).done(function (result) {
$("#dbdata").html("the selected value is " + result.text);
});
}
</script>
Model:
public class InsertDto
{
public string Category { get; set; }
public List<SelectListItem> ListOfCategory { get; set; }
}
controller:
public IActionResult Index(string id)
{
InsertDto i = new InsertDto { ListOfCategory = new List<SelectListItem> { new SelectListItem { Text = "t1" }, new SelectListItem { Text = "t2" }, new SelectListItem { Text = "t3" } } };
return View(i);
}
public SelectListItem GetData(string Category)
{
return new SelectListItem { Text = Category, Value = Category + "value" };
}
result:
I am using iTextSharp to convert a razor view to a pdf that can be downloaded by way of a C# controller. This current implementation works perfectly, however I would like to pass a model from a view to the pdf controller and have the action result download within the browser
Here is my current controller:
public ActionResult Report(MyModel model)
{
// The below method is a custom implementation using iTextSharp
return new FoilPdfActionResult(model, (writer, document) =>
{
document.SetPageSize(PageSize.LETTER.Rotate());
document.NewPage();
})
{
FileDownloadName = "testing.pdf",
};
}
Here is my AJAX call:
function sendForm(model) {
$.ajax({
type: 'POST',
url: '#Url.Action("Report")',
data: model,
success: function (data) {
console.log(data);
},
error: function (xhr, textStatus, errorThrown) {
}
});
}
If I just visit the path of the controller directly at "/Report" it downloads the file correctly within the browser.
If I use the ajax call above to call the controller, it passes the model correctly and returns the pdf result through the data variable on success but does not actually download the pdf. Is is possible to have the pdf downloaded instead of being passed through the data variable?
Recently has a (2) similar situation and found that there are no real baked in ways to do it via ajax. GOOD NEWS: It's actually really easy to do and you do not need an ajax call.
What you can do is submit a form post request and in your controller post action you can generate the file in memory and use "return File()" to pump the file back to the view without the view reloading.
Here is an example using iTextSharp:
Example Model:
public class TestM
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Number { get; set; }
}
View (basic auto generated create)
#model DeleteMeWeb45.Models.TestM
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>TestM</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.FirstName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.FirstName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.FirstName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.LastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.LastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.LastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.Number, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.Number, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.Number, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Controller:
public ActionResult Index()
{
TestM t = new TestM();
return View(t);
}
[HttpPost]
public ActionResult Index(TestM t)
{
if(ModelState.IsValid)
{
Document doc = new Document(iTextSharp.text.PageSize.LETTER, 10, 10, 42, 30);
byte[] pdfBytes;
BaseFont bfTimes = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
Font timesBold = new Font(bfTimes, 12, Font.BOLD);
using (var mem = new MemoryStream())
{
PdfWriter wri = PdfWriter.GetInstance(doc, mem);
doc.SetMargins(20, 20, 20, 60);
doc.Open();
var orderInfoTable = new PdfPTable(2);
orderInfoTable.AddCell(new Phrase("First Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.FirstName, timesBold));
orderInfoTable.AddCell(new Phrase("Last Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.LastName, timesBold));
orderInfoTable.AddCell(new Phrase("Number:", timesBold));
orderInfoTable.AddCell(new Phrase(t.Number.ToString(), timesBold));
doc.Add(orderInfoTable);
doc.Close();
pdfBytes = mem.ToArray();
}
return File(pdfBytes, "application/pdf", "Weeeeeee_" + DateTime.Now.ToString("_MM-dd-yyyy-mm-ss-tt") + ".pdf");
}
else
{
return View(t);
}
}
In the event that you want to keep your form submit button as is (so it saves or does whatever) and want an extra button that just downloads then you would modify the above example as such:
View:
Change 1. Give the form an ID:
#using (Html.BeginForm("Index", "Home", FormMethod.Post, new { id = "transferForm" }))
Change 2. Add a button and JS to submit the form at an alternative location:
<div class="row">
<div class="btn btn-primaryDark btn-sm" id="btnRunReport" style="min-width:120px;">Run Report</div>
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
<script>
$(document).ready(function () {
$("#btnRunReport").click(function () {
$('#transferForm').attr('action', "/Home/JSFUN").submit();
});
});
</script>
}
Controller Changes:
1. Create a new controller action to handle the post request:
[HttpPost]
public ActionResult JSFUN(TestM t)
{
Document doc = new Document(iTextSharp.text.PageSize.LETTER, 10, 10, 42, 30);
byte[] pdfBytes;
BaseFont bfTimes = BaseFont.CreateFont(BaseFont.TIMES_ROMAN, BaseFont.CP1252, false);
Font timesBold = new Font(bfTimes, 12, Font.BOLD);
using (var mem = new MemoryStream())
{
PdfWriter wri = PdfWriter.GetInstance(doc, mem);
doc.SetMargins(20, 20, 20, 60);
doc.Open();
var orderInfoTable = new PdfPTable(2);
orderInfoTable.AddCell(new Phrase("First Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.FirstName, timesBold));
orderInfoTable.AddCell(new Phrase("Last Name:", timesBold));
orderInfoTable.AddCell(new Phrase(t.LastName, timesBold));
orderInfoTable.AddCell(new Phrase("Number:", timesBold));
orderInfoTable.AddCell(new Phrase(t.Number.ToString(), timesBold));
doc.Add(orderInfoTable);
doc.Close();
pdfBytes = mem.ToArray();
}
return File(pdfBytes, "application/pdf", "Weeeeeee_" + DateTime.Now.ToString("_MM-dd-yyyy-mm-ss-tt") + ".pdf");
}
This way your user can use the form submit button and a export button for different purposes.
You can create a Iframe element on page and make it hidden and then bind "src" property with complete path of action, it will download your pdf for sure. I have done this many time. You can take help from this link https://www.codeproject.com/Questions/470316/how-to-download-attachment-using-Iframe
let's suppose I have a form like this:
#using (Ajax.BeginForm("ChangePassword", "User", new { area = "UserSettings" }, new
AjaxOptions
{
HttpMethod = "POST",
OnSuccess = "ShowMessage('Password successfully changed')"
}, null))
{
#Html.TextBoxFor(m => m.Password, new { placeholder = "Password", #class = "form-control", #type = "password" })<br />
#Html.ValidationMessageFor(model => model.Password, "", new { #class = "text-danger", #style = "float:right;" })
#Html.TextBoxFor(m => m.PasswordConfirm, new { placeholder = "Confirm password", #class = "form-control", #type = "password" })<br />
#Html.ValidationMessageFor(model => model.PasswordConfirm, "", new { #class = "text-danger", #style = "float:right;" })
<button type="submit" class="btn btn-primary">Change Password</button>
}
Essentially it's a form to change a password with validation setup in place.
Now my Question here is, in the case where I have an action like this:
[HttpPost]
[ActionName("ChangePassword")]
public ActionResult ChangePassword(MyObjectClass model)
{
if(!ModelState.IsValid)
{
return View(model);
}
else{
ViewBag.MyData = // some data here;
return View();
}
}
The questions that I have are:
When I return a View with all data in it, how do I actually then inject that returned data into the user's DOM that the user can see the changes reflected when OnSuccess method is called? Where is the "data" object that I can inject into like in done function in jQuery( .done(function(data)))?
What is the easiest way here to transfer now all the data from my controller action into the OnSuccess method and actually let the user see the outcome of the method that they called?
Can someone help me out with this ? What is the best way to solve this issue ?
I used to work with pure jQuery before and I Was doing this in this manner:
$.post("/linktomymethod",{/*some parameters*/).done(function(data){
// then inject the returned data into the browser's DOM
}));
If you want to use #Ajax.BeginForm(), then you specify the UpdateTargetId property of AjaxOptions, for example
<div id="changepassword">
#using (Ajax.BeginForm(..., new AjaxOptions { UpdateTargetId = "changepassword" }, null))
{
....
}
<div>
which would replace your <form> element with the view returned by your ChangePassword() method. Note also that your should be returning a PartialView.
However, you should stick with the $.ajax() methods as these give you far more flexibility. Typically you handle the forms .submit() event, check the .valid() method (and cancel the ajax call if not so client side validation messages are displayed), and then update the DOM with the partial view that the method returns.
Replace your #using (Ajax.BeginForm(..) { code with a simple
<div id="changepassword">
#using (Html.BeginForm()) {
....
and add the following script
var url = '#Url.Action("ChangePassword", "User", new { area = "UserSettings" })';
$('form').submit(function (ev) {
ev.preventDefault(); // prevent the default submit
if (!$(this).valid()) { // check if the data is valid
return; // exit the function
}
var formdata = $(this).serialize();`
$.post(url, formdata, function(response) {
$('changepassword').html(response);
});
});
To improve performance further, your could just return a JsonResult containing the invalid properties and their associated error, and update the placeholder elements generated by #Html.ValidationMessageFor(). You controller code would be
if (!ModelState.IsValid)
{
var errors = ModelState.Keys.Where(k => ModelState[k].Errors.Count > 0).Select(k => new { propertyName = k, errorMessage = ModelState[k].Errors[0].ErrorMessage });
return Json(errors);
}
// Save data
return Json(null);
In the success callback, you can then loop through the collection, find the corresponding ValidationMessageFor() placeholder based on the property name, and add the error message and appropriate class names.
Using MVC/Json/Jquery.
Using form to create a new "group".
Form is on ~Group/Manage, posting form to ~Group/Create
Whilst working on this, returning Json result was working fine, handling in Jquery, no URL redirection.
Now, everytime I run it, it redirects me to ~Group/Create and displays the Json result.
Controller Group/Create
[HttpPost]
public ActionResult Create([Bind(Include="name,description")] GroupModel groupmodel)
{
...
return Json(new { success = true, message = groupmodel.name }, JsonRequestBehavior.AllowGet);
}
Form
<form id="frm_createGroup" action="/Groups/Create" method="post">
<h2>Create Group</h2>
<div class="form-group">
#Html.LabelFor(model => model.name, new { #for = "name" })
#Html.TextBoxFor(model => model.name, new { #class = "form-control", #placeholder = "Group Name" })
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="form-group">
#Html.LabelFor(model => model.description, new { #for = "description" })
#Html.TextBoxFor(model => model.description, new { #class = "form-control", #placeholder = "Group Description" })
#Html.ValidationMessageFor(model => model.description)
</div>
<span id="createGroupMessage"></span>
<button type="submit" class="btn btn-primary pull-right">Create</button>
</form>
Jquery to handle form
$(document).ready(function (){
$('#navGroups').makeActiveMenuItem();
var options = {
success: groupCreateSubmitted
,error: groupCreateError
}
$('#frm_createGroup').ajaxForm(options);
});
function groupCreateSubmitted(responseText, statusText, xhr, $form) {
if (responseText.success)
{
$('#createGroupMessage').html = "Group Created";
}
else
{
$('#createGroupMessage').html = responseText.message;
}
}
To be clear, I don't want URL redirection, I just want the Jquery to catch the return (it was before, have no idea why its changed...)
Thanks!
removed
,error: groupCreateError
working now...form bind was failing.
At the moment, I have the following code:
main.js:
$(function () {
var keys = ["test1", "test2", "test3", "test4"];
$("#keywords-manual").autocomplete({
minLength: 2,
source: keys
});
});
Test.cshtml:
#model App.Models.Service
#{
ViewBag.Title = "Test";
}
<script src="~/Scripts/main.js"></script>
<h2>#ViewBag.Title.</h2>
<h3>#ViewBag.Message</h3>
#using (Html.BeginForm("SaveAndShare", "Home", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
<h4>Create a new request.</h4>
<hr />
#Html.ValidationSummary("", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.ServiceType, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextBoxFor(m => m.ServiceType, new { #class = "form-control", #id = "keywords-manual" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" class="btn btn-default" value="Submit!" />
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
The point is that currently I have just provided 4 constant values to the autocomplete. But then I created a database and a table named "services", which comes from my model named Service. I have already provided a few rows to the table with values. I have a field in my table called ServiceType, and I want the autocomplete to take the values of that column as a source. Please note that I have hosted my database in Azure and it is MySQL, though, I think it doesn't matter here. Can you tell me how can I take as a source the values of ServiceType column that is located inside my services table?
As far as I can tell by your question, it should look something like this:
$("#keywords-manual").autocomplete({
source: function (request, response) {
$.ajax({
url: "/Home/GetServiceTypes",
data: "{ 'keywords': '" + request.term + "' }",
dataType: "json",
type: "POST",
contentType: "application/json; charset=utf-8",
dataFilter: function (data) { return data; },
success: function (data) {
response($.map(data, function (item) {
return {
label: item.value,
value: item.value,
id: item.id
}
}))
}
});
},
minLength: 2
});
And the controller,
YourContext db = new YourContext();
public JsonResult GetServiceTypes() {
db.ServiceType.Where(s => keywords == null || s.Name.ToLower()
.Contains(keywords.ToLower()))
.Select(x => new { id = x.ServiceTypeID, value = x.ServiceTypeName }).Take(5).ToList();
return result;
}
Apologies for any typos, but that should be the jist of it. If you need to be searching for more than one keyword, in the controller method, split the value from 'keywords-manual' into a string array, then use a foreach loop or similar approach to match each value, adding matches to a total list each time.
** I say string array, that's pretty oldschool, split it into a list :)