How can I make a repeater type in the page. In the page I have a quantity field:
<td>
#Html.DisplayNameFor(x => x.Quantity)
#Html.ValidationMessageFor(x => x.Quantity)
</td>
<td>
#Html.TextBoxFor(x => x.Quantity, new { #id = "txtQty" })
</td>
When I want to add the item, which there could be several of the same item, just different serial numbers, I need to pop open a div with repeated fields for entering serial numbers:
for (int I = 0; I < *****; I++)
{
<td>Serial Number:</td>
<td>#Html.TextboxFor(x=>x.Quantity, new { #id = "txtQty" + 1})
}
In the JS:
function AddItem() {
Qtys = parseINT($("#txtQty").val());
$("#divSerials").show();
}
How can I do this? Is there a better way?
Is this the way to do it? I try this but 'i' in the HTML model statement is not recognized.
if (parseInt($("#txtQuantity").val()) > 0) {
$("#divSerialNumbers").show();
var html = "<table>";
for (i = 1; i <= serialquantity; i++) {
html += "<tr><td>Serial Number:" + #Html.TextAreaFor(x => x.SerialNumber, new { id = "sns" + i }) + "</td></tr>";
}
html += "</table>";
$("#divSerialNumbers").html(html);
}
Razor code is parsed on the server before it is sent to the view. Javascript is client side code and is not executed until the browser receives the view. This line of code
#Html.TextAreaFor(x => x.SerialNumber, new { id = "sns" + i })
means that on the server you are trying to generate a textarea and set the id attribute to a value that includes a javascript variable which does not yet exist.
Its unclear even what the point of this would be. id attributes serve as selectors in javascript. Whats important is the name and value attributes when it comes to posting your data, and even if it could work, your generating duplicate name attributes which could not bind to you models collection property on post back.
For dynamically generating the html for collections, your name attributes need an indexer, for example <input type="text" name="[0].SerialNumber" />. Options for dynamically creating the html include using the BeginCollectionitem() helper, or a pure client side approach is shown in this answer
If all you are doing is post back an array of strings (the serial numbers) then you could use
var div = $("#divSerialNumbers"); // cache it
$('#Quantity').change(function() { // assumes you remove the pointless 'new { #id = "txtQty" }'
var quantity = parseInt($(this).val()); // use $(this) - don't traverse the DOM all over again
if (!isNan(quantity) && quantity > 0) { // must check for NAN
// note it does not seem necessary to use a table, as opposed to simply adding 4 inputs to the div, but
div.empty(); // clear existing contents
var table = $('</table>');
for (i = 1; i <= quantity; i++) {
var input = $('<input>').attr('name', 'SerialNumber');
var cell = $('</td>').append(input);
var row = $('</tr>').append(cell);
table.append(row);
}
div.append(table).show(); // add the table and display it
}
})
and your controller would need a parameter string[] SerialNumber, for example
public ActionResult Edit(string[] SerialNumber)
Related
I have a Kendo.MVC project. The view has a model with a field of type List<>. I want to populate the List from a Javascript function. I've tried several ways, but can't get it working. Can someone explain what I'm doing wrong?
So here is my model:
public class Dashboard
{
public List<Note> ListNotes { get; set; }
}
I use the ListNotes on the view like this:
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
}
This works if I populate Model.ListNotes in the controller when the view starts...
public ActionResult DashBoard(string xsr, string vst)
{
var notes = rep.GetNotesByCompanyID(user.ResID, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
Dashboard employee = new Dashboard
{
ResID = intUser,
Type = intType,
FirstName = user.FirstName,
LastName = user.LastName,
ListNotes = listNotes
};
return View(employee);
}
... but I need to populate ListNotes in a Javascript after a user action.
Here is my javascript to make an ajax call to populate ListNotes:
function getReminders(e)
{
var userID = '#ViewBag.CurrUser';
$.ajax({
url: "/api/WoApi/GetReminders/" + userID,
dataType: "json",
type: "GET",
success: function (notes)
{
// Need to assign notes to Model.ListNotes here
}
});
}
Here's the method it calls with the ajax call. I've confirmed ListNotes does have the values I want; it is not empty.
public List<Koorsen.Models.Note> GetReminders(int id)
{
var notes = rep.GetNotesByCompanyID(id, 7, 7);
List<Koorsen.Models.Note> listNotes = new List<Koorsen.Models.Note>();
foreach (Koorsen.OpenAccess.Note note in notes)
{
Koorsen.Models.Note newNote = new Koorsen.Models.Note()
{
NoteID = note.NoteID,
CompanyID = note.CompanyID,
LocationID = note.LocationID,
NoteText = note.NoteText,
NoteType = note.NoteType,
InternalNote = note.InternalNote,
NoteDate = note.NoteDate,
Active = note.Active,
AddBy = note.AddBy,
AddDate = note.AddDate,
ModBy = note.ModBy,
ModDate = note.ModDate
};
listNotes.Add(newNote);
}
return listNotes;
}
If ListNotes was a string, I would have added a hidden field and populated it in Javascript. But that didn't work for ListNotes. I didn't get an error, but the text on the screen didn't change.
#Html.HiddenFor(x => x.ListNotes)
...
...
$("#ListNotes").val(notes);
I also tried
#Model.ListNotes = notes; // This threw an unterminated template literal error
document.getElementById('ListNotes').value = notes;
I've even tried refreshing the page after assigning the value:
window.location.reload();
and refreshing the panel bar the code is in
var panelBar = $("#IntroPanelBar").data("kendoPanelBar");
panelBar.reload();
Can someone explain how to get this to work?
I don't know if this will cloud the issue, but the reason I need to populate the model in javascript with an ajax call is because Model.ListNotes is being used in a Kendo Panel Bar control and I don't want Model.ListNotes to have a value until the user expands the panel bar.
Here's the code for the panel bar:
#{
#(Html.Kendo().PanelBar().Name("IntroPanelBar")
.Items(items =>
{
items
.Add()
.Text("View Important Notes and Messages")
.Expanded(false)
.Content(
#<text>
#RenderReminders()
</text>
);
}
)
.Events(e => e
.Expand("getReminders")
)
)
}
Here's the helper than renders the contents:
#helper RenderReminders()
{
if (Model.ListNotes.Count <= 0)
{
#Html.Raw("No Current Messages");
}
else
{
foreach (Note note in Model.ListNotes)
{
#Html.Raw(note.NoteText)
<br />
}
}
}
The panel bar and the helpers work fine if I populate Model.ListNotes in the controller and pass Model to the view. I just can't get it to populate in the javascript after the user expands the panel bar.
Perhaps this will do it for you. I will provide a small working example I believe you can easily extend to meet your needs. I would recommend writing the html by hand instead of using the helper methods such as #html.raw since #html.raw is just a tool to generate html in the end anyways. You can write html manually accomplish what the helper methods do anyway and I think it will be easier for you in this situation. If you write the html correctly it should bind to the model correctly (which means it won't be empty on your post request model) So if you modify that html using javascript correctly, it will bind to your model correctly as well.
Take a look at some of these examples to get a better idea of what I am talking about:
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx
http://blog.stevensanderson.com/2010/01/28/editing-a-variable-length-list-aspnet-mvc-2-style/
So to answer your question...
You could build a hidden container to hold your list values like this (make sure this container is inside the form):
<div id="ListValues" style="display:none">
</div>
Then put the results your ajax post into a javascript variable (not shown).
Then in javascript do something like this:
$('form').off('submit'); //i do this to prevent duplicate bindings depending on how this page may be rendered futuristically as a safety precaution.
$('form').on('submit', function (e) { //on submit, modify the form data to include the information you want inside of your ListNotes
var data = getAjaxResults(); //data represents your ajax results. You can acquire and format that how you'd like I will use the following as an example format for how you could save the results as JSON data: [{NoteID ="1",CompanyID ="2"}]
let listLength = data.length;
for (let i = 0; i < listLength; i++) {
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].NoteID " value="' + data.NoteID +'" />')
$('#ListValues').append('<input type="text" name="ListNotes['+i+'].CompanyID " value="' + data.CompanyID +'" />')
//for your ajax results, do this for each field on the note object
}
})
That should do it! After you submit your form, it should automatically model bind to you ListNotes! You will be able to inpsect this in your debugger on your post controller action.
I want to save any changes made in the textbox field instantaneously to my database, I have the javascript to pass back the changed value to the controller, however I am not aware how pass the unique id with this to save the changed value against.
Below is my view passing the price back to the script, how can I pass the part id with this?
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.partid, new { id = "partCode"})
</td>
<td>
#Html.DisplayFor(modelIem => item.partdesc)
</td>
<td>
#Html.TextBoxFor(modelItem => item.price, new { id = "priceTextBox", #class = "mytext rightJustified" })
</td>
</tr>
}
</table>
<script src="http://code.jquery.com/jquery-latest.js"></script>
<script>
$(function () {
$('#priceTextBox').change(function () {
var changedText = $(this).val();
var partsJava = $("#partCode").val();
//Post the content of your Textbox to your "YourAction" action in "YourController"
$.post('#Url.Action("EnterNewSpecialPrice2", "SpecialPrices")', { "ChangedText": changedText, "PassPartCode": partsJava }, function (data) { });
});
});
</script>
Define a class to hold both pieces of information that matches the Json object you have created in your script:
public class PartInputModel{
public string ChangedText {get;set;}
public string PassPartCode {get;set;}
}
Note the properties above must match the ones you have defined here:
{ ChangedText: changedText, PassPartCode: partsJava }
Also note you must remove the double quotes around the ChangedText and PassPartCode definition when creating the JSON structure as those are not required.
Change your controller action to receive this object and let the model binding do the work:
[HttpPost]
public ActionResult EnterNewSpecialPrice2(PartInputModel inputModel) {
// inputModel will have your two pieces of data
}
A better mechanism for storing and retrieving the id is to attach it as a data tag to the markup for the Text Box by adding a data-id tag:
#Html.TextBoxFor(m => item.price, new { id = "", #class = "priceTextBox mytext rightJustified", data_id="item.partid" })
Then change your script to read this:
var partsJava = $(this).data('id');
The advantage of this approach is that it is very easy to pass additional parameters by simply expanding your JSON structure and adding a matching property to the PartInputModel class.
You generating invalid html because of duplicate id attributes and selectors such as var partsJava = $("#partCode").val(); will only ever get the value of the first element with id="partCode". In anycase, it would be undefined since DisplayFor() does not generate a element (only text) and it has id attribute or value attribute. Change your loop to
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(m => item.partid)</td>
<td>#Html.DisplayFor(m => item.partdesc)</td>
<td>
#Html.TextBoxFor(m => item.price, new { id = "", #class = "priceTextBox mytext rightJustified", data_id="item.partid" })
</td>
</tr>
}
and then modify the script to
$('.priceTextBox').change(function () {
var changedText = $(this).val();
var partsJava = $(this).data('id');
$.post('#Url.Action("EnterNewSpecialPrice2", "SpecialPrices")', { "ChangedText": changedText, "PassPartCode": partsJava }, function (data) { });
});
});
Note the new { id="" } remove the id attribute so you do not get invalid html.
This of course assumes you have a method
public ActionResult EnterNewSpecialPrice2(string ChangedText, string PassPartCode)
in SpecialPricesController (although PassPartCode may be int?
I'm completely stumped. Granted, in java script i'm like that kid trying to jam a square peg into a round hole.
My high level objective: The admins want the ability to edit text surrounding some text boxes, as well as the ability to add and remove 'paragraph'. The reporters and users want the values that are in the textboxes to be used in comparisons, etc (which is the original functionality).
My Solution: This project uses a pretty messy value - attribute table (called an EAV?), which now has fields with associated fields and is self referencing. I decided to leverage this to minimize changes to the database, so the admin essentially creates a string, denotes the places a text box belongs using '{}', and assigns a name to the attribute into text boxes that appear directly below the paragraph.
My Problem: Textboxes generate fine, as soon as the admin stops typing the "{}" count is checked client side, and the correct number of textboxes are added/removed in rows below. However, when the "change" mode (and thereby save the new string) I also want to save the attribute names they selected. I can't seem to get the actual value out of the input. The java script below sends null to elementList. Closer inspection indicates that var fieldNames is getting two elements of "undefined" so it makes sense that I'm getting null. Its also apparent that Its hitting something, becuase the number aligns with there being two 'nameField' rows.
DOM (Hemed down to the essentials)
<tr data-editMode="TextMode" data-ordinal="0">
....
<td>
<a class="changeMode">
<tr class="nameField">
<td colspan='4'>
<input type="text" value="Testing">
<tr class="nameField">
....
Javascript
function getAssociatedTr(row) {
var associatedRows = [];
associatedRows.push(row);
row = row.next('tr');
var hasAnother = true;
while (hasAnother == true) {
if (row != null && row.hasClass("nameField")) {
associatedRows.push(row);
row = row.next('tr');
} else {
hasAnother = false;
}
}
return associatedRows;
}
$(".changeMode").live("click", function () {
var options = $(this).data("options");
var theRow = $(this).closest('tr');
var rows = getAssociatedTr(theRow);
var fieldNames = new Array();
rows.splice(0, 1);
for (var index = 0; index < rows.length; index++) {
{
fieldNames.push(rows[index].next('.nameField').val());
}
}
$(".modal-header", c.rowsModal).html("<h3>Changing edit mode" + options.table + "</h3>");
c.rowsModal.modal("show");
$.ajax({
type: "POST",
traditional: true,
data: { "Name": options.table, "Ordinal": options.row, "EditMode": options.editMode, "ElementNames": fieldNames },
url: "/contracts/changeeditmode/" + c.id.val(),
success: function (data) {
theRow.replaceWith(data);
$.validator.unobtrusive.parse("#supplementForm");
c.rowsModal.modal("hide");
for (j = rows.length - 1 ; j >= 0; j--) {
rows[j].remove();
}
}
});
});
Server side
public ActionResult ChangeEditMode(long id, AddTrackedRowViewModel model,
string editMode, List<string> elementNames)
{
}
As a side note, I'm open to constructive criticism on the JavaScript.
EDIT
I have updated the line to
fieldNames.push(rows[index].nextAll('input').first().val());
But still getting undefined.
SOLUTION
fieldNames.push(rows[index].find("input[type=text]").val());
In this line:
fieldNames.push(rows[index].next('.nameField').val());
you are using the selector ".nameField", but this get a "tr" element, if you want the textbox you need this:
fieldNames.push(rows[index].next('.valid').val());
or using other selector that give you the textbox.
I have an Editor Template which contains a table row with (among other stuff) a dropdown/combobox to select a currency. This edit template is shown many times on the same View and it's possible for a user to add these rows as many times as he wants.
I want changes on a row's dropdown to reflect in an EditorFor (the currency's rate) on the same row, so I've added a onchange html parameter:
<td>
#*#Html.LabelFor(model => model.Currency)*#
#Html.DropDownListFor(model => model.Currency, new SelectList(Model.CurrencyList, "Code", "Code"), new { onchange = "updateCurrency(this)" })
#Html.ValidationMessageFor(model => model.Currency)
</td>
My javascript function makes an ajax call to retrieve the rate for the selected currency:
function updateCurrency(elem) {
alert("Currency changed!")
$.ajax({
type: "GET",
url: "Currency?code=" + elem.value,
success: function (msg) {
// The Rate field's Id:
var RateId = "#Html.ClientIdFor(model=>model.Rate)" // // Halp, problem is here!
document.getElementById(RateId).value = msg;
}
});
}
My problem is that
var RateId = "#Html.ClientIdFor(model=>model.Rate)"
has that Html helper which is server-side code. So when i view the page's source code, the javascript code is replicated (once for each row) and all the var RateId = "#Html.ClientIdFor(model=>model.Rate)" are pointing to the most recently added column's EditorFor.
Probably my way of attempting to solve the problem is wrong, but how can I get my javascript code to update the desired field (i.e. the field in the same row as the changed dropdown list).
I believe that one of the problems is that I have the javasript on the Editor Template, but how could I access stuff like document.getElementById(RateId).value = msg; if I did it like that?
Thanks in advance :)
Figured it out. Hoping it helps somebody:
In my view:
#Html.DropDownListFor(model => model.Currency, new SelectList(Model.CurrencyList, "Code", "Code"), new { #onchange = "updateCurrency(this, " + #Html.IdFor(m => m.Rate) + ", " + #Html.IdFor(m => m.Amount) + ", " + #Html.IdFor(m => m.Total) + ")" })
In a separate JavaScript file:
function updateCurrency(elem, RateId, AmountId, TotalId) {
var cell = elem.parentNode // to get the <td> where the dropdown was
var index = rowindex(cell) // get the row number
// Request the currency's rate:
$.ajax({
blah blah blah .........
(RateId[index - 1]).value = 'retreived value'; // Set the rate field value.
});
}
Seems to be working so far.
I'm trying to do something that seems like it should be easy but I'm new to MVC and convention-based programming.
I have a jQuery datatable that is getting rows for PDF documents through AJAX. In the fnRowCallback, I added checkboxes so that the user can select multiple documents to be combined for a single download. As checkboxes are checked, the document ID is added to a JavaScript array of numbers and the filenames are added to another array so that, when combined, they can be used for bookmarks in the resulting PDF. Is there any way to send these two variables to the controller action? So far, all I've been able to do is JSON.stringify() one of the variables and send it to the controller using a hidden field in a form I put on the view and deserializing it in the controller but I'm goofing it up when I try to add the second variable. There has to be an easier way but I can't even figure out the convoluted way and all articles I've read use AJAX. I can't use AJAX, though, because you can't send a binary file back in the response.
JavaScript:
var aiSelectedPDFs = new Array();
var aiSelectedDocumentIDs = new Array();
$('#imgDownload').click(function () {
$('#selectedPDFs').val(JSON.stringify(aiSelectedPDFs));
$('#selectedDocumentIDs').val(JSON.stringify(aiSelectedDocumentIDs));
$('#DownloadSelectedPdfs').submit();
});
View:
<img id="imgDownload" src="#(Url.RootUrl())Content/images/icons/pdf.gif"
alt="Download selected documents" title="Download selected documents" />
#using (Html.BeginForm("DownloadSelectedPdfs", "Controller", FormMethod.Post,
new { id = "DownloadSelectedPdfs" }))
{
<input type="hidden" id="selectedPdfs" name="jsonSelectedPdfs"/>
<input type="hidden" id="selectedDocumentIDs" name="jsonSelectedDocumentIDs"/>
}
Controller:
[HttpPost]
public ActionResult DownloadSelectedPdfs(string jsonSelectedDocumentIDs)
{
var selectedDocumentIDs = new JavaScriptSerializer().Deserialize<int[]>(
jsonSelectedDocumentIDs);
var invoices = new Dictionary<string, byte[]>();
foreach (int documentID in selectedDocumentIDs)
{
invoices.Add(documentID.ToString(),
_documentService.GetDocument(documentID));
}
return new FileContentResult(PdfMerger.MergeFiles(invoices),
"application/pdf");
}
Your answer is 90% correct, Kroehre. Thank you for such a quick response. The only issue is that the application cannot figure out what controller action to use so the page fails to load and I get redirected to my friendly error page. The solution to that was pretty simple, though, and I'll put the code below.
View (I left out the divs as I felt they sullied up the code with presentation semantics where nothing was to be displayed, though they had no syntactical impact. Just personal preference so not part of the missing 10%. ;-) ):
<img id="imgDownload" src="#(Url.RootUrl())Content/images/icons/pdf.gif"
alt="Download selected documents" title="Download selected documents" />
#using (Html.BeginForm("DownloadSelectedPdfs", "Controller", FormMethod.Post,
new { id = "DownloadSelectedPdfs" })) { }
Script (Created similar multiple hidden inputs but with naming such that they were the same object with properties):
var aiSelectedPDFs = new Array();
var aiSelectedDocumentIDs = new Array();
$('#imgDownload').click(function () {
var form = $('#DownloadSelectedPdfs');
form.html('');
for (var i = 0; i < aiSelectedPDFs.length; i++) {
form.append('<input type="hidden" name="selectedPDFs[' + i + '].RefNumber"
value="' + aiSelectedPDFs[i] + '" />');
form.append('<input type="hidden" name="selectedPDFs[' + i + '].DocumentID"
value="' + aiSelectedDocumentIDs[i] + '" />');
}
form.submit();
});
Controller (added new class to handle the multiple, related javascript variables):
public class PDFViewModel
{
public int RefNumber { get; set; }
public int DocumentID { get; set; }
}
[HttpPost]
public ActionResult DownloadSelectedPdfs(List<PDFViewModel> selectedPDFs)
{
var pdfs = new Dictionary<string, byte[]>();
foreach (var selectedPDF in selectedPDFs)
{
var document = _documentService.GetDocument(selectedPDF.DocumentID);
var tabName = string.Format("pdf_{0}", selectedPDF.RefNumber);
pdfs.Add(tabName, document);
}
return new FileContentResult(PdfMerger.MergeFiles(pdfs), "application/pdf");
}
Arrays can be transmitted via form by specifying the array name and index for each value. I've modified your code:
View (replaced your hidden inputs with containers):
<img id="imgDownload" src="#(Url.RootUrl())Content/images/icons/pdf.gif"
alt="Download selected documents" title="Download selected documents" />
#using (Html.BeginForm("DownloadSelectedPdfs", "Controller", FormMethod.Post,
new { id = "DownloadSelectedPdfs" }))
{
<div id="pdfs"></div>
<div id="docs"></div>
}
Script (adding / populating hidden inputs for items in the arrays instead of using stringification):
var aiSelectedPDFs = new Array();
var aiSelectedDocumentIDs = new Array();
function createInputs(container, name, values){
$(container).html('');
for(var i = 0; i<values.length; i++){
$(container).append('<input type="hidden" name="' + name + '[' + i + ']" value="' + values[i] + '" />');
}
}
$('#imgDownload').click(function () {
createInputs('#pdfs', 'selectedPdfs', aiSelectedPDFs);
createInputs('#docs', 'selectedDocumentIDs', aiSelectedDocumentIDs);
$('#DownloadSelectedPdfs').submit();
});
Controller (updating input parameters to line up with posted arrays, leveraging MVC3's built in model binder):
[HttpPost]
public ActionResult DownloadSelectedPdfs(List<int> selectedPdfs, List<int> selectedDocumentIDs)
{
var invoices = new Dictionary<string, byte[]>();
foreach (int documentID in selectedDocumentIDs)
{
invoices.Add(documentID.ToString(),
_documentService.GetInvoice(documentID));
}
return new FileContentResult(PdfMerger.MergeFiles(invoices),
"application/pdf");
}
*Note - I used List<int> for the parameters instead of int[] because the type must be able to add elements after it is instantiated in order for the model binder to function properly as its reading the values that were posted.