Return a Javascript array to action - javascript

Here's my problem:
i have a JS array that i want to pass it to an action and then render that action's View.
this is my array and i fill it Using Jquery like so :
var factorlist = [];
factorlist.push({ ID: data.ID, Name: data.Name, number: data.number, SingelPrice: data.SingelPrice, TotalPrice: data.TotalPrice })
(data come from an AJAX Call)
then i put a hidden input element in my page to put my array in it and send it with a submit.
here is hidden input and submit button :
<input type="submit" id="input" name="input" />
<input type="hidden" id="list" name="list"/>
This is how i send it :
$('form').submit(function (event) {
$('#list').val(JSON.stringify(factorlist));
});
and this is the action that i'm sending the array to :
public ActionResult PrePayment(List<Order> list)
{
return View(list);
}
and my order class :
public class Order
{
public int ID { get; set; }
public string Name { get; set; }
public int number { get; set; }
public float SingelPrice { get; set; }
public float TotalPrice { get; set; }
}
**Now the thing is i get and empty list in action not null...what is the problem and is there any other way to do this? **

The default MVC data binding will not work for you in this case, you have to deserialize your values manually:
var js = new JavaScriptSerializer();
list = (Order[])js.Deserialize(Request.Form["list"], typeof(Order[]));
To avoid doing this every time you can register a Custom Model Binding for that type too, check an example in http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder

You should use Json instead of JS array. More details http://msdn.microsoft.com/en-us/library/system.web.mvc.jsonresult(v=vs.118).aspx

if i understand you right, you want to post your js array from form to controller action, so basing on this article there is a solution:
$('form').submit(function (event) {
$(factorlist).each(function (i, el) {
$('form').append($('<input />', {
type: 'hidden',
name: 'list[' + i + '].ID',
value: el.ID
}));
// and so on for each your of objects property
});
});
More correct way is to do it using ajax and redirect on success callback.

Related

Trying to post a list collection to the controller with ajax

I'm trying to post a list collection to the controller with a full page post back to the server. The data from the client side is written with JavaScript / jQuery and the server code is written in C# using ASP.Net MVC.
I'm not sure what the issue is because when the ItemCheck method is hit with a breakpoint on the first curly brace after the method name but the model object Items property is null even though it's in the form data mentioned further below.
The code below is simplified for ease and is not working.
Input hidden field in the view where the JavaScript adds an item to it each time add button is pressed.
#model UI.ViewModels.ItemResource
#Html.HiddenFor(model => model.Items, new { #id = "Items" })
<button class="btn-default btn" id="AddItem" name="AddItem" type="button" disabled>Add</button>
JavaScript
var items = [];
// This code only adds an item to the items collection input hidden
// field for a full page post back to the server.
var addItemToList = function () {
var item = {
ItemDate: $(date).val(),
ItemAmount: $(amount).val()
};
items.push(item);
$("#Items").val( JSON.stringify(items));
};
Controller
public ActionResult ItemCheck(ItemViewModel model)
{
if (result.IsValid)
{
model.Items = _anotherViewModel.Items;
_anotherViewModel.Items= null;
model.IsValid = _itemService.Save(model, _Itemhelper.GetUserId());
}
else
{
//Validation here
}
return View(model);
}
ViewModels
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace UI.ViewModels.ItemResource
{
public class ItemsViewModel
{
public ItemsViewModel()
{
Items = new List<ItemViewModel>();
}
[JsonBindable]
public List<ItemViewModel> Items { get; set; }
}
public class ItemViewModel
{
public DateTime ItemDate { get; set; }
public decimal ItemAmount { get; set; }
}
}
FormData
Items: [{"ItemDate":"2020-06-05","ItemAmount":"2267"}]

Use javascript variable in c#

Is it possible to use js variables in c# code?
This is my model:
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual ICollection<Department> Parent { get; set; }
}
And this is my cshtml:
#model List<inspinia.models.Department>
<select name="DepartmentId" class="form-control" id="Department">
#foreach (var item in Model)
{
<option value="#item.Id">#item.Name</option>
}
</select>
<script>
$(document).ready(function () {
$("#Department").on("change", function () {
var DepartmentId = $('option:selected', this).attr('value');
//the problem in the next line.
#foreach(var item in Model.Where(x=>x.ParentId==#:DepartmentId))
{
#:alert(#item.id)
}
});
});
</script>
Well I am having error when I use
Model.Where(x=>x.ParentId==#:JAVASCRIPT VARIABLE).
Is there any way to do it or I definitely have to use ajax ?
No you can't, because the javascript code's is for clientside and razor in serverside.
You have two solutions:
1- Put all departement in list(<ul>) and use JQuery for get your data on change
2- Use jquery ajax and get data directly from controller(create controller for that)

ListBoxFor add data attribute to each option element

I have a MultiSelectList that I am currently using to create a ListBoxFor in my view. On my ViewModel I have an additional property on each of the items in the list that I would like to expose in the HTML for client side manipulation.
I have a checkbox above this list. When a user clicks the checkbox only data-x="true" items should show in this list. If the user does not click the checkbox, all items are shown (data-x="true" and data-x="false"). This logic is done client side each time the checkbox it changed
I am assuming the best way to do this would be to add a data-* attribute to each of the option elements. How exactly would I go about doing this? I am thinking an HTMLHelper method would be needed to extend the functionality of ListBoxfor but I am not sure where to get started, or if this is even the best way to go about this?
To be able to generate <option> elements with a data=* attribute, you would need to create you own class based on SelectListItem with additional properties for the attributes, and then generate you own HtmlHelper extension method based on the code in the System.Web.Mvc.SelectExtensions class.
A far easier way to handle this would be to pass collection you use to build the SelectList to the view, assign it to a javascript array and then use javascript/jquery to handle the checkbox .click() event and rebuild the list of options in the <select> element. In your case, it could simply be a case of hiding/showing items based on the value of the checkbox.
For example, if you have a view to select a product, and include a checkbox to limit that selection to only locally made products, your models might be
public class MyViewModel
{
public bool UseLocalProductOnly { get; set }
public int SelectedProduct{ get; set; }
public IEnumerable<SelectListItem> ProductList { get; set; }
public IEnumerable<int> LocalProducts { get; set; }
}
public class Product
{
public int ID { get; set; }
public string Name { get; set; }
public bool IsLocalProduct { get; set; }
}
and in the controller
var products = db.Products;
MyViewModel model = new MyViewModel()
{
ProductList = new SelectList(products, "ID", "Name"),
LocalProducts = products.Where(x => x.IsLocalProduct).Select(x => x.ID)
}
return View(model);
and in the view
#Html.CheckBoxFor(m => m.UseLocalProductOnly)
#Html.DropDownListFor(m => m.SelectedProduct, Model.ProductList)
<script>
var localProducts = #Html.Raw(Json.Encode(Model.LocalProducts))
var select = $('#SelectedProduct');
var options = select.children('option');
$('#UseLocalProductOnly').click(function() {
if ($(this).is(':checked')) {
// reset the selected option if it would otherwise be hidden
if ($.inArray(Number(select.val()), localProducts) === -1) {
select.val(localProducts[0]);
}
// hide options that are not local products
$.each(options, function () {
if ($.inArray(Number($(this).val()), localProducts) === -1) {
$(this).hide();
}
});
} else {
// show all options
productOptions.show();
}
});
</script>
Alternatively you could use .prop('disabled', true); (or false) to enable or disable options rather that hiding them.

How to get collection of IDs, and Selected Dropdown values passed from View's table to Controller?

So I have a checklist/task manager kind of application that basically pulls a bunch of string values from a database using a model, and then pipes it out to a view which uses a expand/collapse patterned table. The only element that an end user modifiers beyond the expand/collapse state is the dopdown list for the project status.
Rather than an update button for each row, the customer wants basically a single button on the bottom of the page which updates all the rows the user changed. So my goal is to try and create said button. As far as the View code snippet, I've tried messing around with Html.BeginForm, but haven't been able to figure out how to also capture the multiple item IDs as well:
#using (Html.BeginForm("UpdateDB", "Checklist", new { id = model.ID })) // model does not exist in this current context
{
<table id="checklistGrid" class="table table-bordered table-striped grid">
<tr>
<th>#Html.DisplayNameFor(model => model.ww)</th>
.... // more table headers
</tr>
#foreach (var group in Model.GroupBy(x => x.ww))
{
<tr class="group-header">
<td colspan="12">
<span class="h2">#group.Key</span>
</td>
</tr>
foreach (var dept in group.GroupBy(x => x.dept))
{
<tr class="dept-header">
<td colspan="12">
<span class="h4">#dept.Key</span>
</td>
</tr>
foreach (var item in dept)
{
<tr class="row-header">
<td> #Html.HiddenFor(modelItem => item.ID)</td>
<td>#Html.DisplayFor(modelItem => item.ww)</td>
.... // more table columns
<td>
#{
List<string> ddl = new List<string> { "Done", "Not Done", "N/A" };
SelectList sl = new SelectList(ddl, item.status);
#Html.DropDownList("newStatus", sl); // Added
}
</td>
<td>#Html.DisplayFor(modelItem => item.applied_by)</td>
<td>
#{
DateTime tmp;
//Check if not null, if not null convert to specified time zone
if (DateTime.TryParse(item.timestamp.ToString(), out tmp))
{
tmp = item.timestamp ?? DateTime.MinValue;
string zoneName = "India Standard Time";
var abbrZoneName = TimeZoneNames.TimeZoneNames.GetAbbreviationsForTimeZone(zoneName, "en-US");
TimeZoneInfo zoneInfo = TimeZoneInfo.FindSystemTimeZoneById(zoneName);
DateTime istTime = TimeZoneInfo.ConvertTimeFromUtc(tmp, zoneInfo);
#Html.Raw(istTime.ToString() + " (" + abbrZoneName.Generic + ")");
}
else
{
#Html.DisplayFor(modelItem => item.timestamp);
}
}
</td>
</tr>
}
}
}
</table>
<p><input type="submit" value="Save Changes" /></p>
}
<script type="text/javascript">
// Hide/Show Dept's Data on Click
$(function () {
$('.dept-header').click(function () {
$(this).nextUntil('.dept-header, .group-header').toggle();
});
});
$(function () {
$('.group-header').click(function () {
var elm = $(this);
if (elm.next().is(":visible")) {
elm.nextUntil('.group-header').hide();
} else {
elm.nextUntil('.group-header', '.dept-header').show();
}
});
});
</script>
I suspect the easier (and computationally quicker way) would to be to use Jquery + Ajax && Json attached to a button onclick at the bottom to send the collection of id and selected dropdown text to an ActionResult in the Controller (to then update the database) and then refresh on the success Ajax callback. However my Jquery/Javascript foo is weak and given the clickable 'header' type rows that expand/collapse the data rows, it's not clear to me how I would efficiently use Jquery to navigate and grab the item.IDand the selected dropdown text from each row, bundle it up, and pipe to the desired ActionResult.
So how do I send both the id and selected dropdown text of all the rows to an arbitrary ActionResult be it with Html.BeginForm or Jquery et al?
EDIT: Model for reference:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace TaskTracker.Models
{
[Table("some_update")]
public class TaskInfo
{
public int ID { get; set; }
[Display(Name = "WW")]
public int ww { get; set; }
[Display(Name = "File Name")]
public string filename { get; set; }
[Display(Name = "Dept")]
public string hsd_unit { get; set; }
[Display(Name = "Owner")]
public string owner { get; set; }
[Display(Name = "Manager")]
public string manager { get; set; }
[Display(Name = "Project")]
public string project { get; set; }
[Display(Name = "Status")]
public string status { get; set; }
[Display(Name = "Last Applied By")]
public string applied_by { get; set; }
[Display(Name = "Last Updated Time Stamp")]
public DateTime? timestamp { get; set; }
}
public class TaskInfoDBContext : DbContext
{
public DbSet<TaskInfo> TaskInfoSet { get; set; }
}
}
You have a number of problems with you view including your form controls have duplicate name attributes meaning you cannot bind to you model when you submit, duplicate id attributes which is invalid html, and your creating a dropdownist with a name that is not even a property of your model. In addition your use of queries in the view code is not good practice. All of this can be solved by using view models and generating your html correctly using for loops (not foreach loops) or a custom EditorTemplate. However, since you have indicated a preference for ajax, you can post the data by making the following changes to the view
Replace the following HtmlHelper methods
#Html.HiddenFor(modelItem => item.ID)
#Html.DropDownList("newStatus", sl)
with
#Html.HiddenFor(modelItem => item.ID, new { id = "", #class = "id" })
#Html.DropDownList("Status", sl, new { id = "", #class = "status" })
This will remove the invalid id attributes and add class names for selection. Then replace the submit button with
<button type="button" id="save">Save Changes</button>
And add the following script
var rows = $('.row-header'); // store all the table rows containing the form controls
$('#save').click(function() {
// Build an array of the values to post back
var data = [];
$.each(rows, function() {
data.push({id: $(this).find('.id').val(), status: $(this).find('.status').val() });
});
$.ajax({
url: '#Url.Action("UpdateDB", "Checklist")',
type: "POST",
data: JSON.stringify({ model: data },
traditional: true,
contentType: "application/json; charset=utf-8",
success: function(data) {
// do something?
}
})
});
Then create a model to accept the values
public class TaskInfoUpdate
{
public int ID { get; set; }
public string Status { get; set; }
}
And modify you POST method to
[HttpPost]
public ActionResult UpdateDB(IEnumerable<TaskInfoUpdate> model)
Side note: Its not clear what you mean by "and then refresh on the success Ajax callback" (what is there to refresh?). I suggest that the method return either return Json(true); if the items were successfully saved, or return Json(null); (or a HttpStatusCodeResult) if not so that you can notify the user as appropriate.

Find ids of checked checkboxes with jQuery in MVC

I have a Field model, which represents a certain field (Name, Description, ...)
class FieldModel : EntityModel
{
...
public bool ToCopy { get; private set; }
public string Id {get; private set; }
...
}
An Index model, which has a collection of Fields:
class EntityModel
{
...
}
class IndexModel
{
public IEnumerable<EntityModel> Fields { get; private set; }
}
Controller for copy, that should accept ids of fields to copy:
public void CopyFields(string[] fieldsIds)
{
...
}
And I need to select certain fields to copy by checkboxes. So in the vew for Field I added
#Html.CheckBoxFor(x => x.IsSelectedForCopy)
In the view for Index
<button onclick="onCopyClick('#Model');" type="button" class="btn showButton">Copy Fields</button>
And now I need to write a script to select all checked fields and send their Ids to the controller. I have zero experience with Javascript/jQuery, so could someone help me with that?
This should get you started at least ;)
You give jQuery some css selectors and it gives you the objects that match...
$("input :checked").each(function() {
alert($(this).attr("id"));
});
Depending on how you want to send them, you could then append each id to a hidden field on a form like so:
$("input :checked").each(function() {
var tmp = $("#myHiddenField").val();
tmp += " " + $(this).attr("id"));
$("#myHiddenField").val(tmp);
});
$.ajax("TheURLToPostTheDataTo",
{data: [
{idsToSend:$("#myHiddenField").val()}
],
success: function() {
alert("Done");
}
});
Then submit, and on the serverside trim and split by space?

Categories