0

I currently have a foreach loop in my MVC view that creates a table row for each each with a drop down list that need to execute a JavaScript during the onchange event. This onchange event will populate another drop down list within the same table row based on the selected value.

var count = 0;

@foreach(var item in Model.Items)
{
    <tr>
        <td>
            @Html.DropDownListFor(
                model => item.SelectedId,
                new SelectList(item.MyList, "ID", "Name"),
                new 
                { 
                    @class = "form-control", 
                    @id = string.Concat("MyControl", count),
                    @name = string.Concat("MyControl", count),
                    @onchange = string.Format("javascript:MyJsFunction(this.value, {0});", count);"
                }
            )
            @Html.DropDownListFor(
                model => item.ChildSelectedId, 
                new SelectList(item.MyChildList, "ID", "Name"),
                new 
                {
                    @class = "form-control",
                    @id = string.Concat("MyChildControl", count),
                    @name = string.Concat("MyChildControl", count)
                }
             )
        </td>
    </tr>

    count++;
}

At the bottom of the view, I have the following <script> block:

<script>
    $(document).ready(function () {
        //do some other stuff here
    });

    function MyJsFunction(value, id) {
        var loadingMsg = "<option value='0'>Loading...</option>";
        var url = "/MyApp/Controller/Action/";

        var targetControl = "#MyChildControl" + id

        $(targetControl).html(loadingMsg).show();

        $.ajax({
            url: url,
            data: { Id: value },
            cache: false,
            type: "POST",
            success: function (data) {
                var markup = "<option value='0'>- My Child Control - </option>";

                for(var i = 0; i < data.length, i++) {
                    markup += "<option value=" + data[i].Value + ">" + data[i].Text + "</option>";
                }

                $(targetControl).html(markup).show();
            },
            error: function (response) {
                alert("error: " + response);
            }
        });
    }
</script>

When I run my code and change the value in the drop down list, I get the following error:

Uncaught ReferenceError: MyJsFunction is not defined

How do I resolve this error?

Michael Kniskern
  • 24,792
  • 68
  • 164
  • 231
  • 3
    Why not give all the elements a common class and attach an event handler with jQuery? That would negate the need for both the incremental id and also the outdated `onchange` event attribute – Rory McCrossan Sep 07 '16 at 21:45
  • @RoryMcCrossan The drop down list will populate another drop down list within the same table row associated with the selected element from the first dropdown – Michael Kniskern Sep 07 '16 at 21:48
  • Can you provide the final html code? – Dekel Sep 07 '16 at 21:49
  • @dekel Yes, I will update the source to reflect the end result. – Michael Kniskern Sep 07 '16 at 21:51
  • 2
    @MichaelKniskern Ok, but that doesn't affect the pattern I described in my previous comment – Rory McCrossan Sep 07 '16 at 21:52
  • BTW, I copied and pasted your code to a sample project and it worked perfectly fine. No errors – Shyju Sep 07 '16 at 21:53
  • @Shyju - Did you put the function inside or outside the `document.ready` code block? – Michael Kniskern Sep 07 '16 at 22:04
  • outside ! There is no reason to put it inside as that code is not binding any events to the dom elements. – Shyju Sep 07 '16 at 22:07
  • @RoryMcCrossan So I would create one common for all dynamically generated controls? How would it know which child control to target when I change a value for a specific control? – Michael Kniskern Sep 07 '16 at 22:11
  • By looking it seems to me that `$(targetControl)` is not correct, targetControl gives you `"MyChildControl" + id` which jquery cant select. If it is an id you are access by prepend `#` to it or if class prepend `.` and try.. – CNKR Sep 07 '16 at 22:14
  • @MichaelKniskern you would use something like [`.closest()`](http://api.jquery.com/closest/) or [`.next()`](http://api.jquery.com/next/) to target the elements that are within the current row. Here's [an example](http://stackoverflow.com/a/33660811/361762) – Dave Sep 07 '16 at 22:15
  • 2
    @MichaelKniskern use `this` to reference the element that raised the event and jQuery's DOM traversal methods, eg `$(this).next('.foo')` – Rory McCrossan Sep 07 '16 at 22:25
  • Not related, but your use of a `foreach` loop means you cannot bind to a model (refer [this answer](http://stackoverflow.com/questions/30094047/html-table-to-ado-net-datatable/30094943#30094943)) and the dropdownlists will correctly select the value of any existing `SelectedId` value (refer [this answer](http://stackoverflow.com/questions/37407811/mvc5-razor-html-dropdownlistfor-set-selected-when-value-is-in-array/37411482#37411482)) and your use of `@name = string.Concat("MyControl", count),` does nothing (it will not change the `name` attribute) –  Sep 07 '16 at 22:36
  • As @RoryMcCrossan has note, use class names and relative selectors –  Sep 07 '16 at 22:37
  • @user3498863 I check my code and I have the `#` in my original code. typo in the question – Michael Kniskern Sep 07 '16 at 23:01
  • @RoryMcCrossan - I can't get to the function because it is not defined. According to the browser. Can you provide an example in the answer for me to try? I am not grasping the concept cause I guess I am using "old school" JS code architecture. – Michael Kniskern Sep 07 '16 at 23:07
  • @MichaelKniskern, Its simply `$('.ClassNameOfFirstSelect').change(function() { var target = $(this).closest('tr').find('.ClassNameOfSecondSelect'); .... });` and remove the `onchange` attribute –  Sep 07 '16 at 23:13
  • @StephenMuecke Where should that code snippet go? – Michael Kniskern Sep 07 '16 at 23:37
  • Inside ` –  Sep 07 '16 at 23:38
  • @StephenMuecke I added that snippet of code to the `script` section in my page and it did resolve the error, but how do I wired it up to execute the function to populate the child drop down list? – Michael Kniskern Sep 07 '16 at 23:46
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/122868/discussion-between-stephen-muecke-and-michael-kniskern). –  Sep 07 '16 at 23:47

1 Answers1

0

Working with Stephen Muecke, he helped me develop the following code snippet to resolve my problem:

HTML:

@foreach(var item in Model.Items)
{
    <tr>
        <td>
            @Html.DropDownListFor(
                model => item.SelectedId,
                new SelectList(item.MyList, "ID", "Name"),
                new 
                { 
                    @class = "form-control parent", 
                    @id = ""
                }
            )
            @Html.DropDownListFor(
                model => item.ChildSelectedId, 
                new SelectList(item.MyChildList, "ID", "Name"),
                new 
                {
                    @class = "form-control child",
                    @id = ""
                }
            )
        </td>
    </tr>
}

JavaScript:

<script>
    $('.parent').change(function() {
        var url = "/MyApp/Controller/Action/";
        var id = $(this).val();
        var target = $(this).closest('tr').find('.child');

        target.empty();

        $.ajax({
                url: url,
                data: { Id: id },
                cache: false,
                type: "POST",
                success: function (data) {
                    target.append($('<option></option>').val('').text('- Child Control -'));
                    $.each(data, function (index, item) {
                        target.append($('<option></option>').val(item.Value).text(item.Text));
                    });
                },
                error: function (response) {
                    alert("error: " + response);
                }
            });
    });
</script>
Community
  • 1
  • 1
Michael Kniskern
  • 24,792
  • 68
  • 164
  • 231