1

Currently I am working on displaying different input forms on tabs in asp.net mvc 3. Each tab has several child partial views. [See the image below]
1 of the partial views contains a form for creating Sessions, and above it is another partial view with a list of sessions created (none yet).. When switching tabs at the top, and back again the jquery timepicker plugin by Francois Gelinas known as jquery.ui.timepicker.js fails to rebind/reinitialize after selecting another tab and then returning to the tab Edit Course and Program Defaults.
I'm using the razor engine, instead of the default web engine...

The masterpage/file _Layout.cshtml

  <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">  
  <html>  
  <head>  
   <meta http-equiv="X-UA-Compatible" content="IE=8" />    
   <title>@ViewBag.Title</title>  
   <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")"  type="text/javascript">   </script>  
   <script src="@Url.Content("~/Scripts/jquery.ui.core.js")" type="text/javascript">   </script>   
   <script src="@Url.Content("~/Scripts/jquery.livequery.js")" type="text/javascript">  </script>  
   <script src="@Url.Content("~/Scripts/jquery.ui.widget.js")" type="text/javascript"></script>  
   <script src="@Url.Content("~/Scripts/jquery.ui.tabs.js")" type="text/javascript"></script>  
   <script src="@Url.Content("~/Scripts/jquery.ui.timepicker.js")" type="text/javascript"></script>  
   <script src="@Url.Content("~/Scripts/jquery-ui-1.8.11.custom.js")" type="text/javascript"></script>  

   <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />  
   <link href="@Url.Content("~/Content/jquery-ui-1.8.11.custom.css")" type="text/css" rel="stylesheet" />  
   <link href="@Url.Content("~/Content/jquery-ui-timepicker.css")" type="text/css" rel="stylesheet" />  

   <script src="@Url.Content("~/Scripts/MicrosoftAjax.debug.js")" type="text/javascript"></script>
   <script src="@Url.Content("~/Scripts/MicrosoftMvcAjax.debug.js")" type="text/javascript"></script>  

Index.cshtml << this is called by the create button on the main mvc application menu
It creates the 3 tabs you see in the picture above.

@using MvcHtmlHelpers;
<link href="@Url.Content("~/Content/BoxBackgrounds.css")" rel="stylesheet" type="text/css" />


@{
    ViewBag.Title = "Index";
 }

<h2>Index</h2>
<div id="tabs">
 <ul>
   <li >@Html.TabLink("Edit Student Defaults", "EditStudentDefaults", "Creation") </li>
   <li>@Html.TabLink("Edit Exam and Certification Defaults", "EditCertAndExamDefaults", "Creation") </li>
  <li>@Html.TabLink("Edit Course and Program Defaults", "EditCourseAndProgramDefaults", "Creation")</li>
 </ul>
</div>

<script type="text/javascript" language="javascript">

$(document).ready(function() {
    $("#tabs").tabs({
    cache: false
   });

 });

EditCourseAndProgramDefaults.cshtml << partial view tab with partial views inside it.

@model SMS3demo.Controllers.CourseAndProgramDefaultsViewModel
<table width="100%" class="ConfigStudentsGradient" cellspacing="0" border="0" frame="void" style="border-collapse:collapse">
 <tr>
     <td>
        @Html.Partial("ListSessions")
     </td>
     <td>
        @Html.Partial("ListCourses")
     </td>
     <td>
        @Html.Partial("ListPrograms")
    </td>


 </tr>
 <tr>
     <td>
        @Html.Partial("AddSession")
     </td>
     <td>
         @Html.Partial("AddCourse")
     </td>
     <td>
         @Html.Partial("AddProgram")
     </td>

 </tr>

</table>

AddSession.cshtml << partial view that uses the timepicker

@model SMS3demo.Controllers.CourseAndProgramDefaultsViewModel  
@using (Html.BeginForm()) {
<script type="text/javascript">
 $(document).ready(function () {
     $('#StartTime').timepicker({
         showPeriod: true,
         showLeadingZero: false
     });
 });

@Html.ValidationSummary(true)
<fieldset>
    <legend>Session</legend>
        <table>
<tr>
                <td>
                    <span class="SessionStartTime">@Html.TextBoxFor(model => model.SessionStartTime, new {@id = "StartTime"  }) </span>


                    @Html.ValidationMessageFor(model => model.SessionStartTime)
                </td>

I researched this topic for a couple of days now. I tried the following ideas/options methods...

  1. using the live
  2. using livequery
  3. trying to recreate the binding after each onclick event for the input field
  4. i tried to see how others solved this problem using another control like the datepicker, since this plugin is very similar to it.

I know what is happening: the timepicker is losing its binding, when i switch back to the tab. I discovered in firebug there is a hidden timepicker object created, and it doesn't die when the tabs are switched... however, I cannot seem to rebind or recall the original method to recreate when the tab is selected again. I know document.ready is only called 1x when the partial view is 1st loaded...
I tried doing the following: in

<script type="text/javascript">
    $(document).ready(function() {
        initJQueryWidgets();
    });

    function initJQueryWidgets() {
        $("[ID$=StartTime1]").timepicker({
                showPeriod: true,
                showLeadingZero: false,
                zIndex: 1
            });
    }


</script>

I also tried at the top ofthe page in the script bracket show above

Sys.WebForms.PageRequestManager.getInstance().add_endRequest(function() {
         initJQueryWidgets();
    });

I then tried calling the initJqueryWidgets in the onClick even handler for the input box

 <span class="SessionStartTime">@Html.TextBoxFor(model => model.SessionStartTime, new {@id = "StartTime", @onClick="initJqueryWidgets"  }) </span>

The errors I consistently received were $("#StartTime").timepicker is not a function (?)()52 (line 2) anonymous()Creation (line 29) [Break On This Error] $("#StartTime").timepicker({showPeriod: true, showLeadingZero: false});

Alexander O'Mara
  • 58,688
  • 18
  • 163
  • 171
Fletch
  • 21
  • 3

3 Answers3

1

The timepicker create an instance in the DOM once and use this instance every time it is displayed except for inline display, that's why you see it in your DOM, it does not mean that it detached from the input.

If the timepicker is now showing on click, that probably mean that there is a zIndex problem or it lost its click event.

(document).ready(function () {
 $('#StartTime').timepicker({
     showPeriod: true,
     showLeadingZero: false,
     zIndex: 9999
 });

});

If the #StartTime is loosing it's binding, you have to remove the "hasTimePicker" class to it before binding it again.


I'll investigate tonight as to how the datepicker react in this situation and make it work on the timepicker too, will update when I find the solution.

  • What should I call to rebind it to the input? Any attempts to call $('#StartTime').timepicker(); results in a error timepicker is not a function... and then of course clicking on the input box doesn't do anything. – Fletch Mar 28 '11 at 23:32
  • Sorry my attempt at recreating the problem failed. I understand the content is loaded dynamically in the tabs but that should not be a problem. Do you get more then 1 element with id StartTime ?. --- a test page with the jQuery tabs and the timepicker where I tried to reproduce the problem : http://fgelinas.com/code/timepicker/tab_bug/ – Francois Gelinas Mar 29 '11 at 00:23
  • Hehe.. I'm learning firebug, jquery, and asp.net mvc all at the same time! :) I played in firebug some more.. I found out. 1. the class is lost on the input box when the tab is shown again. using firebug/firequery i added the class back in realtime of course this doesn't rebind the event... :( I only have 1 element with that id of StartTime. No class naming conflicts either... So once its bound.. hidden, redisplayed its no longer bound to the click event, how do we rebind it? cannot use the same call used originally, do youhave a hidden method inthe class i should be calling? – Fletch Mar 29 '11 at 00:28
  • Nice, your example works... I think the difference though is i've got the control nested deeper? Does your document.ready function get called every time the tab gets the focus again? – Fletch Mar 29 '11 at 00:38
  • The #StartTime input you see the second time is not the same instance as the first one (unbinded and no hasTimepicker class). You should reattach the timepicker to it. Now you were getting "timepicker is not a function" because either the script is not loaded or $ is not jQuery anymore. Maybe try jQuery('#StartTime').timepicker(); – Francois Gelinas Mar 29 '11 at 00:45
  • Okay, I did some more debugging... Moral of the story, watch your scripts you include, and watch the versions... Whew! Hope this helps someone else... In some of the other tabs they linked in the 1.4 version, but in the root page/masterpage i linked in 1.5... looking at the console in firebug and watching the get calls and thinking about it I saw it! Thank you for taking time to help me, and help me! – Fletch Mar 29 '11 at 01:36
1

i fixed it, by removing the calls to load other jquery versions! Check your Firebug console when you click your tabs, and see what get commands are being processed. Make sure you link in only 1 version of jquery! Whew! Also, I found that in partial views remove all script link references, and put in in the masterpage, easier to track and correct.

Special Thanks to Francois Gelinas for taking time out of his day to help, and also to Black Box Operations for helping as well.

Fletch
  • 21
  • 3
0

Jquery tabs often disable the div containing the elements for each tab when it's not being viewed. That said, if Jquery can't find the element then it can't create the date picker, so you'll have to fire off an event when the tab is selected to first check to see if the element exists then create the date picker accordingly.

$('#tabs a#idOfTabContainingStartTime').bind('click', function(){
    if( $("#StartTime").length ){
         $('#StartTime').timepicker({
             showPeriod: true,
             showLeadingZero: false
         });
    }
}

If that doesn't solve your problem, then it's probably because Jquery gets a bit trigger happy with the whole tabs/div/hiding thing. When you select another tab the tab you were currently viewing is hidden, but Jquery also likes to hide everything within the tab as well, so you may have to create another call that checks for any elements currently being hidden within the tab and iteratively show them.

$('#tabs a#idOfTabContainingStartTime :hidden').each(function(){
    $(this).show();
});