6

There is a lot to commend MVC, but one problem I keep getting is ID name collisions. I first noticed it when generating a grid using a foreach loop. With the help of SO I found the solution was to use Editor Templates. Now I have the same problem with tabs. I used this reference to find out how to use tabs; http://blog.roonga.com.au/search?updated-max=2010-06-14T19:27:00%2B10:00&max-results=1

The problem with my tabs is that I am using a date field with a date picker. In the example above, ID name collisions are avoided by referencing a generated unique Id of the container element. However for a datepicker, the ID of the container is irrelevant, only the ID of the date field matters. So what happens is that if I create my second tab the same as the first, when I update my second tab, the date on the first is updated. So below is my View, and a partial view which displays the date. When I click the "Add Absence for 1 day button, I create a tab for that screen;

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/AdminAccounts.master" 
Inherits="System.Web.Mvc.ViewPage<SHP.WebUI.Models.AbsenceViewModel>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
 AbsenceForEmployee
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="AdminAccountsContent" runat="server">
    <script type="text/javascript">
        $(function () {
        $('#tabs').tabs(
            { cache: true },
            {
                ajaxOptions: {
                    cache: false,
                    error: function (xhr, status, index, anchor) {
                        $(anchor.hash).html("Couldn't load this tab.");
                    },
                    data: {},
                    success: function (data, textStatus) { }
                },
                add: function (event, ui) {
                    //select the new tab
                    $('#tabs').tabs('select', '#' + ui.panel.id);
                }
            });
        });

        function addTab(title, uri) {
            var newTab = $("#tabs").tabs("add", uri, title);
        }

        function closeTab() {
            var index = getSelectedTabIndex();
            $("#tabs").tabs("remove", index)
        }

        function getSelectedTabIndex() {
            return $("#tabs").tabs('option', 'selected');
        }

        function RefreshList() {
            $('#frmAbsenceList').submit();
        }
 </script>
    <% using (Html.BeginForm()) {%>
    <%: Html.AntiForgeryToken() %>
        <fieldset>
        <legend>Select an employee to edit absence record</legend>
        <div style="padding-bottom:30px;padding-left:10px;">
    <div class="span-7"><b>Name:</b> <%: Model.Employee.GetName() %></div>
    <div class="span-6"><b>Division:</b><%: Model.DivisionName %></div>
    <div class="span-6"><b>Department:</b> <%: Model.DepartmentName %></div></div>   
    <p>Attendance record for the year <%: Html.DropDownListFor(model => model.SelectedYearId, Model.YearList, new { onchange = "this.form.submit();" })%></p> 
    <div id="tabs">
     <ul>
      <li><a href="#tabs-1">Absence List</a></li>
     </ul>
     <div id="tabs-1">
            <input id="btnAddOneDayTab" type="button" onclick="addTab('Add Absence (1 day)','<%= Url.Action("AddAbsenceOneDay", "Employee")  %>')" value='Add Absence for 1 day' />
            <input id="btnAddDateRangeTab" type="button" onclick="addTab('Add Absence (date range)','<%= Url.Action("AddAbsenceDateRange", "Employee")  %>')" value='Add Absence for a range of dates' />
            <hr />
      <% Html.RenderPartial("ListAbsence", Model.ListEmployeeAbsenceThisYear); %>
     </div>
    </div>
    </fieldset>
        <% } %>

Add new date partial view ...

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SHP.Models.EmployeeOtherLeaf>" %>
<% var unique = DateTime.Now.Ticks.ToString(); %>
<script language="javascript" type="text/javascript">
    $(document).ready(function () {
        $('#frmAddAbsenceOneDay<%= unique %> #NullableOtherLeaveDate').datepicker({ dateFormat: 'dd-MM-yy' });
        $('#frmAddAbsenceOneDay<%= unique %> #MorningOnlyFlag').click(function () {
            $('#frmAddAbsenceOneDay<%= unique %> #AfternoonOnlyFlag').attr('checked', false);
        })
        $('#frmAddAbsenceOneDay<%= unique %> #AfternoonOnlyFlag').click(function () {
            $('#frmAddAbsenceOneDay<%= unique %> #MorningOnlyFlag').attr('checked', false);
        })
    });

    var options = {
        target: '#frmAddAbsenceOneDay<%= unique %>',
        success: RefreshList
    };

    $(document).ready(function () {
        $('#frmAddAbsenceOneDay<%= unique %>').ajaxForm(options);
    });

</script>

<div id="AddAbsenceOnDay<%= unique %>">

    <% using (Html.BeginForm("AddAbsenceOneDay", "Employee", FormMethod.Post,
           new { id = "frmAddAbsenceOneDay" + unique }))
       { %>
        <%: Html.ValidationSummary(true) %>
        <fieldset>
            <legend>Add an absence for a day or half day</legend>
            <table>
                <tr>
                    <td><%: Html.LabelFor(model => model.OtherLeaveId)%></td>
                    <td>
                <%: Html.DropDownListFor(model => model.OtherLeaveId, Model.SelectLeaveTypeList, "<--Select-->")%>
                <%: Html.ValidationMessageFor(model => model.OtherLeaveId)%>                    
                    </td>
                </tr>
                <tr>
                    <td>
                <%: Html.LabelFor(model => model.NullableOtherLeaveDate)%>                    
                    </td>
                    <td>
                <%: Html.EditorFor(model => model.NullableOtherLeaveDate)%>
                <%: Html.ValidationMessageFor(model => model.NullableOtherLeaveDate)%>
                <%if (ViewData["ErrorDateMessage"] != null && ViewData["ErrorDateMessage"].ToString().Length > 0)
                  { %>   
                                   <p class="error">
                   At <% Response.Write(DateTime.Now.ToString("T")); %>. <%: ViewData["ErrorDateMessage"]%>.
                </p>
                <%} %>                 
                    </td>
                </tr>
                <tr>
                    <td>
                <%: Html.LabelFor(model => model.MorningOnlyFlag)%>
                    </td>
                    <td>
                <%: Html.CheckBoxFor(model => model.MorningOnlyFlag)%>
                <%: Html.ValidationMessageFor(model => model.MorningOnlyFlag)%>                        
                    </td>
                </tr>
                <tr>
                    <td>
                <%: Html.LabelFor(model => model.AfternoonOnlyFlag)%>
                    </td>
                    <td>
                <%: Html.CheckBoxFor(model => model.AfternoonOnlyFlag)%>
                <%: Html.ValidationMessageFor(model => model.AfternoonOnlyFlag)%>                     
                    </td>
                </tr>
            </table>

            <p>
                <span style="padding-right:10px;"><input type="submit" value="Create" /></span><input type="button" value="Close" onclick="closeTab()" />
            </p>
        </fieldset>

    <% } %>
    </div>

You can see the ID of the date in the following HTML from Firebug

<div id="main">

    <div id="adminAccounts">
    <table>
        <tbody><tr>
            <td> 

<div style="padding-top: 15px;">
    // Menu removed
</div>
</td>
            <td>
    <script type="text/javascript">
        $(function () {
        $('#tabs').tabs(
            { cache: true },
            {
                ajaxOptions: {
                    cache: false,
                    error: function (xhr, status, index, anchor) {
                        $(anchor.hash).html("Couldn't load this tab.");
                    },
                    data: {},
                    success: function (data, textStatus) { }
                },
                add: function (event, ui) {
                    //select the new tab
                    $('#tabs').tabs('select', '#' + ui.panel.id);
                }
            });
        });

        function addTab(title, uri) {
            var newTab = $("#tabs").tabs("add", uri, title);
        }

        function closeTab() {
            var index = getSelectedTabIndex();
            $("#tabs").tabs("remove", index)
        }

        function getSelectedTabIndex() {
            return $("#tabs").tabs('option', 'selected');
        }

        function RefreshList() {
            $('#frmAbsenceList').submit();
        }
    </script>
    <form method="post" action="/Employee/AbsenceForEmployee?employeeId=1"><input type="hidden" value="8yGn2w+fgqSRsho/d+7FMnPWBtTbu96X4u1t/Jf6+4nDSNJHOPeq7IT9CedAjrZIAK/DgbNX6idtTd94XUBM5w==" name="__RequestVerificationToken">
        <fieldset>
        <legend>Select an employee to edit absence record</legend>
        <div style="padding-bottom: 30px; padding-left: 10px;">
    <div class="span-7"><b>Name:</b> xaviar  caviar</div>
    <div class="span-6"><b>Division:</b>ICT</div>
    <div class="span-6"><b>Department:</b> ICT</div></div>   
    <p>Absence Leave record for the year <select onchange="this.form.submit();" name="SelectedYearId" id="SelectedYearId"><option value="2" selected="selected">2010/11</option>
</select></p> 
    <div id="tabs" class="ui-tabs ui-widget ui-widget-content ui-corner-all">
        <ul class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all">
            <li class="ui-state-default ui-corner-top"><a href="#tabs-1">Absence List</a></li>
        <li class="ui-state-default ui-corner-top"><a href="#ui-tabs-2"><span>Add Absence (1 day)</span></a></li><li class="ui-state-default ui-corner-top ui-tabs-selected ui-state-active"><a href="#ui-tabs-4"><span>Add Absence (1 day)</span></a></li></ul>
        <div id="tabs-1" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide">
            <input type="button" value="Add Absence for 1 day" onclick="addTab('Add Absence (1 day)','/Employee/AddAbsenceOneDay')" id="btnAddOneDayTab">
            <input type="button" value="Add Absence for a range of dates" onclick="addTab('Add Absence (date range)','/Employee/AddAbsenceDateRange')" id="btnAddDateRangeTab">
            <hr>

<script type="text/javascript">

    var options = {
        target: '#AbsenceList'
    };

    $(document).ready(function() {
        $('#frmAbsenceList').ajaxForm(options);
    });

</script>
<div id="AbsenceList">
<table class="grid"><thead><tr><th class="sort_asc"><a href="/Employee/AbsenceForEmployee?Direction=Descending&amp;employeeId=1"></a></th><th><a href="/Employee/AbsenceForEmployee?Column=OtherLeaveName&amp;Direction=Ascending&amp;employeeId=1">Leave Type</a></th><th><a href="/Employee/AbsenceForEmployee?Column=MorningOnlyFlag&amp;Direction=Ascending&amp;employeeId=1">Morning Only</a></th><th><a href="/Employee/AbsenceForEmployee?Column=AfternoonOnlyFlag&amp;Direction=Ascending&amp;employeeId=1">Afternoon Only</a></th><th><a href="/Employee/AbsenceForEmployee?Column=DayAmount&amp;Direction=Ascending&amp;employeeId=1">Day Amount</a></th><th><a href="/Employee/AbsenceForEmployee?Column=OtherLeaveDate&amp;Direction=Ascending&amp;employeeId=1">Date</a></th></tr></thead><tbody><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=60">Delete</a></td><td>Sickness</td><td>False</td><td>False</td><td>1</td><td>04/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=51">Delete</a></td><td>Unpaid Sickness</td><td>False</td><td>False</td><td>1</td><td>08/11/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=54">Delete</a></td><td>Unpaid Compassionate</td><td>False</td><td>False</td><td>1</td><td>09/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=45">Delete</a></td><td>Compassionate</td><td>False</td><td>False</td><td>1</td><td>10/11/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=43">Delete</a></td><td>Compassionate</td><td>False</td><td>False</td><td>1</td><td>15/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=55">Delete</a></td><td>Unpaid Sickness</td><td>False</td><td>False</td><td>1</td><td>16/11/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=56">Delete</a></td><td>Compassionate</td><td>False</td><td>False</td><td>1</td><td>17/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=44">Delete</a></td><td>Compassionate</td><td>False</td><td>False</td><td>1</td><td>22/11/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=37">Delete</a></td><td>Unpaid Sickness</td><td>False</td><td>False</td><td>1</td><td>24/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=36">Delete</a></td><td>Sickness</td><td>False</td><td>False</td><td>1</td><td>25/11/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=48">Delete</a></td><td>Unpaid Sickness</td><td>False</td><td>False</td><td>1</td><td>26/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=38">Delete</a></td><td>Unpaid Sickness</td><td>False</td><td>False</td><td>1</td><td>29/11/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=5">Delete</a></td><td>Compassionate</td><td>False</td><td>False</td><td>1</td><td>30/11/2010</td></tr><tr class="gridrow_alternate"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=46">Delete</a></td><td>Unpaid Sickness</td><td>False</td><td>False</td><td>1</td><td>13/12/2010</td></tr><tr class="gridrow"><td><a href="/Employee/DeleteAbsence?_employeeOtherLeaveId=61">Delete</a></td><td>Compassionate</td><td>False</td><td>False</td><td>1</td><td>26/12/2010</td></tr></tbody></table>
        <p></p><div class="pagination"><span class="paginationLeft">Showing 1 - 15 of 21 </span><span class="paginationRight">first | prev | <a href="/Employee/AbsenceForEmployee?page=2&amp;employeeId=1">next</a> | <a href="/Employee/AbsenceForEmployee?page=2&amp;employeeId=1">last</a></span></div>

</div>



        </div><div id="ui-tabs-2" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide">


<div id="AddAbsenceOnDay634255177533718544">

    <form method="post" id="frmAddAbsenceOneDay634255177533718544" action="/Employee/AddAbsenceOneDay">
        <fieldset>
            <legend>Add an absence for a day or half day</legend>
            <table>
                <tbody><tr>
                    <td><label for="OtherLeaveId">Leave Type</label></td>
                    <td>
                <select name="OtherLeaveId" id="OtherLeaveId"><option value="">&lt;--Select--&gt;</option>
<option value="1">Sickness</option>
<option value="2">Unpaid Sickness</option>
<option value="6">Compassionate</option>
<option value="7">Unpaid Compassionate</option>
<option value="8">Unauthorised</option>
<option value="9">Unpaid Unauthorised</option>
<option value="10">Other</option>
<option value="11">Unpaid Other</option>
</select>

                    </td>
                </tr>
                <tr>
                    <td>
                <label for="NullableOtherLeaveDate">Date</label>                    
                    </td>
                    <td>
                <input type="text" value="" name="NullableOtherLeaveDate" id="NullableOtherLeaveDate" class="datePicker hasDatepicker">





                    </td>
                </tr>
                <tr>
                    <td>
                <label for="MorningOnlyFlag">Morning Only</label>
                    </td>
                    <td>
                <input type="checkbox" value="true" name="MorningOnlyFlag" id="MorningOnlyFlag"><input type="hidden" value="false" name="MorningOnlyFlag">

                    </td>
                </tr>
                <tr>
                    <td>
                <label for="AfternoonOnlyFlag">Afternoon Only</label>
                    </td>
                    <td>
                <input type="checkbox" value="true" name="AfternoonOnlyFlag" id="AfternoonOnlyFlag"><input type="hidden" value="false" name="AfternoonOnlyFlag">

                    </td>
                </tr>
            </tbody></table>

            <p>
                <span style="padding-right: 10px;"><input type="submit" value="Create"></span><input type="button" onclick="closeTab()" value="Close">
            </p>
        </fieldset>

    </form>
    </div>

</div><div id="ui-tabs-4" class="ui-tabs-panel ui-widget-content ui-corner-bottom">


<div id="AddAbsenceOnDay634255177583860349">

    <form method="post" id="frmAddAbsenceOneDay634255177583860349" action="/Employee/AddAbsenceOneDay">
        <fieldset>
            <legend>Add an absence for a day or half day</legend>
            <table>
                <tbody><tr>
                    <td><label for="OtherLeaveId">Leave Type</label></td>
                    <td>
                <select name="OtherLeaveId" id="OtherLeaveId"><option value="">&lt;--Select--&gt;</option>
<option value="1">Sickness</option>
<option value="2">Unpaid Sickness</option>
<option value="6">Compassionate</option>
<option value="7">Unpaid Compassionate</option>
<option value="8">Unauthorised</option>
<option value="9">Unpaid Unauthorised</option>
<option value="10">Other</option>
<option value="11">Unpaid Other</option>
</select>

                    </td>
                </tr>
                <tr>
                    <td>
                <label for="NullableOtherLeaveDate">Date</label>                    
                    </td>
                    <td>
                <input type="text" value="" name="NullableOtherLeaveDate" id="NullableOtherLeaveDate" class="datePicker hasDatepicker">





                    </td>
                </tr>
                <tr>
                    <td>
                <label for="MorningOnlyFlag">Morning Only</label>
                    </td>
                    <td>
                <input type="checkbox" value="true" name="MorningOnlyFlag" id="MorningOnlyFlag"><input type="hidden" value="false" name="MorningOnlyFlag">

                    </td>
                </tr>
                <tr>
                    <td>
                <label for="AfternoonOnlyFlag">Afternoon Only</label>
                    </td>
                    <td>
                <input type="checkbox" value="true" name="AfternoonOnlyFlag" id="AfternoonOnlyFlag"><input type="hidden" value="false" name="AfternoonOnlyFlag">

                    </td>
                </tr>
            </tbody></table>

            <p>
                <span style="padding-right: 10px;"><input type="submit" value="Create"></span><input type="button" onclick="closeTab()" value="Close">
            </p>
        </fieldset>

    </form>
    </div>

</div>
    <div id="ui-tabs-1" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"></div><div id="ui-tabs-3" class="ui-tabs-panel ui-widget-content ui-corner-bottom ui-tabs-hide"></div></div>
    </fieldset>
        </form></td>
        </tr>
    </tbody></table></div>

            <div id="footer">
            </div>
        </div>

If you have got this far(!), I have been asked for the controller, so here it is.

[Authorize(Roles = "Administrator, AdminAccounts, ManagerAccounts")]
public ActionResult AddAbsenceOneDay()
{
    return View(new EmployeeOtherLeaf());
}

[HttpPost]
[Authorize(Roles = "Administrator, AdminAccounts, ManagerAccounts")]
public ActionResult AddAbsenceOneDay(EmployeeOtherLeaf _absence)
{
    if (ModelState.IsValid)
    {
        _absence.EmployeeId = SessionObjects.EmployeeId; 
        _absence.OtherLeaveDate = _absence.NullableOtherLeaveDate.GetValueOrDefault(DateTime.Today);
        Tuple<bool, string> errorInfo = _absence.IsDateValid();
        if (errorInfo.Item1 == true)
        {
            _absence.AddAndSave();
            ViewData["SuccessMessage"] = "Successfully Added.";
            return View("EditAbsenceOneDay", _absence);
        }
        else
        {
            ViewData["ErrorDateMessage"] = errorInfo.Item2;
            return View(_absence);
        }
    }
    else
    {
        return View(_absence);
    }
}
arame3333
  • 9,887
  • 26
  • 122
  • 205
  • Can you please supply the HTML generated from this page (scrubbed if necessary of course)? That would really help determine exactly why you are having this problem. Also, once you edit the question, please reply to this comment using the @NickLarsen symbol so it pings me that I have a message. – Nick Larsen Nov 16 '10 at 13:52
  • @NickLarsen I hope this helps. I haven't found a good way to retrieve HTML from Firebug. – arame3333 Nov 16 '10 at 14:20
  • In firebug select the main div and click the edit button. It will be a bit messy though. This picture is really small though and very hard to read. – gligoran Nov 16 '10 at 15:14
  • @gligoran - useful tip. I hope the HTML is not too much. Hopefully you can see 2 tabs of the same type. They have different form Ids, but the same ID for "NullableOtherLeaveDate". I can use the container Id of the form to interact with the flag fields, but cannot do so for the date field. So the datepicker is activated by clicking on the date field in either tab, but only updates the date field in the first tab. Their Ids are the same, so only the first one is updated. – arame3333 Nov 16 '10 at 15:36
  • What does your controller look like? The answer is very dependent on what kind of model binding you are looking for. – John Farrell Nov 16 '10 at 17:03
  • This is actually a really good question. I have some similar questions I will post tonight and when I figure it out, I'll follow up here. – Nick Larsen Nov 16 '10 at 21:13

2 Answers2

1

The problem seems to be that the DOM only has one entry for NullableOtherLeaveDate. This seems logical because of the use of ID. What you can to is add the hash to the ID as well. If you need to match that ID with any jQuery you can use partial selectors like this:

$('input[id*=NullableOtherLeaveDate]')

See jQuery Partial Selectors for more about that. Now how the modal binder will bind that I'm not sure but you can probably implement partial matching in C# with no problems. If you want any help with this please post the relevant code of your action.

Community
  • 1
  • 1
gligoran
  • 3,267
  • 3
  • 32
  • 47
  • I am not sure how this would work. If I update the date field with a datepicker in the second tab, how would I use this to stop the update happening for the date in the first tab? What I need to do is make the ID unique. However it is useful to know about partial selectors, so thank you for showing me that. – arame3333 Nov 17 '10 at 09:01
  • There shouldn't be 2 items in the DOM with the same ID at all. This is what I understand is asking how to achieve it. Your solution is just a hack to not get -ve effect of doing it wrong. – Meligy Mar 15 '11 at 03:12
1

The answer to this other question suggests using editor templates instead of partials to solve the problem.

MVC - fields in a partial View need a Unique Id. How do you do this?

Of course this will not work for every situations, just the editor ones.

Update:

There is another easy way too. The ViewData has a nice property you can use to set a prefix that makes the partial unique, then this ViewDataDictonary can be passed to the partial.

 var  dataDictionary = new ViewDataDictionary<PersonModel>(Model.Persons.First);
 dataDictionary.TemplateInfo.HtmlFieldPrefix = "Persons.First";

See the following links for details on this:
- http://forums.asp.net/t/1627585.aspx/1
- http://forums.asp.net/t/1624152.aspx

Notice this overload of Html.Partial():

Partial(HtmlHelper, String, Object, ViewDataDictionary)

http://msdn.microsoft.com/en-us/library/ee407417.aspx

Community
  • 1
  • 1
Meligy
  • 35,654
  • 11
  • 85
  • 109