1

On a page in a Razor MVC app a bunch of lines are written from a model, like this:

@foreach (var item in Model)
    {
    // write lots of data
    @Html.CheckBoxFor(item.Id) // incorrect syntax, I know
    }
<input type="button" value="Update" onclick="Update();" />

The javascript that is run from the button is supposed to get the item.Id (which is a unique integer) from each checkbox that has been checked, put the ids in an array and pass the array to another function. This is what I'm working with so far:

function Update() {
    var ids = document.getElementsByName("myCheckbox");
    var idList = new Array(ids.length);
    for (var i = 0; i < ids.length; i++) {
        if (ids[i].checked {
        idList.push(ids[i]);
        }
    }
    $.ajax({
        type: "POST",
        url: "@Url.Action("Update", "Home")",
        data: idList,
        success: function (data) {
        },
        error: function (data) {
            console.log(data);
        }
    });
}

How do I add the same name to all the checkboxes and have their ID be that of item.Id?

Edit

I tried C Murphy's suggestion by making a class for the checkboxes:

    public class ToBeUpdated
    {
        public int ID { get; set; }
        public bool IsSelected { get; set; }
    }

And then adding a List to the class that's in the model:

        public List<ToBeUpdated> ToBeUpdatedList { get; set; }

However, I can't figure out how to write the CheckBoxFor. His/her suggestion was:

@Html.CheckboxFor(modelItem => modelItem.SomeNameList[i].IsSelected)

But I'm not doing a for loop but a foreach:

foreach (var item in Model)

So I had to do this instead:

@Html.CheckBoxFor(modelItem => item.ToBeUpdatedList.Find(m => m.ID == item.Id).IsSelected)

Then I have my button that sends me to the function in my controller:

<input type="button" value="Update" onclick="location.href='@Url.Action("UpdateStatuses", "Home")'" />

        public IActionResult UpdateStatuses(IEnumerable<FileData> model)
        {
            // iterate through the items in the model
        }

The problem is that when I get to UpdateStatuses the model is empty even though I had a ton of checkboxes that I checked before clicking the button. The model in the View is

@model IEnumerable<MyData>

And MyData is a simple class:

    public class MyData
    {
        public int Id { get; set; }
        public List<ToBeUpdated> ToBeUpdatedList { get; set; }
    }

Edit 2: Finally found a solution

I made my checkboxes like this:

<input type="checkbox" name="chkUpdateThis" id="@item.Id" />

And then I could, with the help of T.J's code, edit my javascript to do this:

    function UpdateStatuses() {
        const ids = [...document.querySelectorAll("[name=chkUpdateThis]")].filter(({checked}) => checked).map(({id}) => id)
        var idList = new Array();
        for (var i = 0; i < ids.length; i++) {
            idList.push(parseInt(ids[i]));
        }
        $.ajax({
            type: "POST",
            url: "@Url.Action("UpdateStatuses", "Home")",
            data: ({ ids: idList }),
            success: function (data) {
            },
            error: function (data) {
                console.log(data);
            }
        });
    }

Then I can call the UpdateStatuses function in my Home controller:

public IActionResult UpdateStatuses(List<int> ids)

Since T.J didn't post a suggestion that I can give a checkbox to, I'll give it to C Murphy who was also helpful. I didn't use his suggestion, but I don't want to give myself a checkbox even though the solution I went for is right here.

N. Little
  • 69
  • 9
  • 1
    *"How do I add the same name to all the checkboxes and have their ID be that of item.Id?"* Add the name where? Are you having trouble generating the page (in .Net), or working with the JavaScript? (Side note: Your JavaScript code is missing a `)` after `if (ids[i].checked`.) – T.J. Crowder Feb 12 '20 at 14:24
  • 3
    Why not just add a class to all of the checkboxes you're targeting and use that? – Heretic Monkey Feb 12 '20 at 14:25
  • 1
    Side note: If you're using a modern browser, getting the `idList` can be simpler: `const idList = [...document.querySelectorAll("[name=myCheckBox]")].filter(({checked}) => checked).map(({id}) => id);`. :-) With slightly older browsers, you have to polyfill iterability on `NodeList` [as I show here](https://stackoverflow.com/a/46057817/157247). On any version of IE, though, what you have (adding the missing `)`) is probably best. – T.J. Crowder Feb 12 '20 at 14:26
  • T.J. I haven't run the code since it's not compiling so the javascript code is just written off the top of my head to show my thinking. My problem is getting the unique id's from the checked checkboxes. In order to get all the checkboxes I was thinking I would somehow give them all the same name and then get an array of them by using document.getElementsByName. – N. Little Feb 12 '20 at 17:27
  • T.J so how do I name all my checkboses "myCheckBox" and how do I get the checkboxes to hold the unique IDs? – N. Little Feb 13 '20 at 08:35
  • T.J., in your example, how should I write the code for the checkbox? @Html.CheckBox(For?)...? – N. Little Feb 13 '20 at 10:14

2 Answers2

2
@Html.CheckBoxFor(model => model.Id, new { Name = "myCheckbox" })
apomene
  • 14,282
  • 9
  • 46
  • 72
  • Looks promising. I'll try that tomorrow. – N. Little Feb 12 '20 at 17:28
  • I can't get this to work. It seems to me that CheckBoxFor takes an expression (in your example "model => model.Id" that resolves to a bool that tells CheckBoxFor if the checkbox should be checked or not. So model => model.Id (or in my case: modelItem => (model.Id)) doesn't compile. Also, I only want unchecked checkboxes since the user decides if they should be checked or not. – N. Little Feb 13 '20 at 08:34
1

On a similar note to what Heretic Monkey commented, you should probably set up a class. Without knowing what your Update ActionResult is doing in your controller, I can't give you a complete answer, but you could follow this general structure:

Model:

public class SomeNameViewModel
{
     public List<SomeName> SomeNameList { get; set; }
}

public class SomeName
{
     public int Id { get; set; }
     public bool IsSelected { get; set; }
}

View:

@model ProjectName.Models.SomeNameViewModel

@for (var i = 0; i < Model.Count(); i++)
{
     <table>
          <tr>
               <td>@Html.HiddenFor(modelItem => modelItem.SomeNameList[i].Id)</td>
               <td>@Html.CheckboxFor(modelItem => modelItem.SomeNameList[i].IsSelected)</td>
          </tr>
     </table>                  
}

Edit (due to information provided via comment):

(note I also adjusted the previous section to use a ViewModel instead of a list of the new class)

You would then adjust your ActionResult to instead accept the ViewModel class as an argument and perform the database update as follows:

Controller:

public ActionResult Update(SomeNameViewModel viewModel)
{
     List<int> selectedIds = new List<int>();

     foreach(var row in viewModel.SomeNameList)
     {
          if(row.IsSelected == true)
          {
               selectedIds.Add(row.Id);
          }
          else
          {
               // wasn't selected, so do nothing
          }
     }

     // Now you have a list of Ids (selectedIds) that were selected 
     // and can then be passed to your other function
}

Second Edit:

I think there is still some confusion surrounding the business requirement around what you are trying to accomplish.

The reason for the error was I assumed you would be passing those IDs (in the form of the ToBeUpdated view model class) to the ActionResult, instead of passing in the IEnumerable<FileData>. That is why it is empty when it reaches your controller. The structure of MVC (Model View Controller) dictates that you pass information in the form of a model (class) to the controller from the view (and vice versa).

Please provide your full model list along with a more detailed explanation of what you are trying to achieve and why.

The reason why I utilized a for loop instead of a foreach loop is to ensure that the list is serialized with unique indexes for each item in the list. The only reason you need to use checkboxes is to identify which IDs are to be updated. The logic to determine which are going to be updated should be used on the controller side. Think of a checkbox as an input field for a boolean (true/false, 0/1) property. The goal of a unique ID is just to identify your unique data records. The boolean (checkbox field on the view side) is just to have your end user designate a specific record(s) as needing to be updated.Whatever your model may be, you should add a boolean property (like I gave as an example) to have the user determine which record(s) to update, and call the SQL update command from your controller ONLY on the records from your list/IEnumerable that have a value of true for your IsSelected property.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
C Murphy
  • 47
  • 1
  • 13
  • 1
    The Update ActionResult just takes the list/array of ints (item.Id) and updates each item in the database (and then possibly returning the user to the same page). So it's important that the list/array of ints only contains the ints for the lines where the checkbox has been checked. – N. Little Feb 12 '20 at 17:31
  • 1
    Thank you for your time and knowledge. I will try this and the other suggested solution tomorrow. – N. Little Feb 12 '20 at 20:27
  • I tried this but can't quite get it to work. I edited my original post to show what I did and what goes wrong. – N. Little Feb 13 '20 at 09:40