Locked jQuery Mobile Control issue

  • Thursday, January 24, 2013 2:32 AM
     
     

    Hi all,

    I am reffering to this article from LS team blog.

    I tested jQuery control binding in my application in different dialogs. It allows me flip switch value to true (linked to boolean field in dialog) and save changes back to database. But when I open dialog again I can see that control shows false value again. When I debug createBooleanSwitch function I can see that all values are equal to true. Why that control does reflect data binding?



All Replies

  • Wednesday, January 30, 2013 10:41 AM
     
     
    Any idea? It is still out of order.
  • Wednesday, January 30, 2013 8:01 PM
    Moderator
     
     

    Hi Andrew,

    If you created an empty project with the createBooleanSwitch function copy-pasted from the tutorial, would that work?  If you say you've debugged the method and that the setFlipSwitchValue function is getting a "true" value passed in, sound like databinding is working fine.  Are you sure you have the right version of jQueryMobile, for instance, and that it's getting initialized in the right place?

    BTW, you can always manually call the refresh method (see http://jquerymobile.com/demos/1.1.1/docs/forms/slider/methods.html), but that shouldn't be necessary.  I would try things out on a new empty project, and see if you can get it to work there.

    If you're still having issues, even with the small simple project, could you put it up on SkyDrive or somewhere where we can take a look at it?

    Best of luck,

    - Michael


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

  • Wednesday, January 30, 2013 8:50 PM
     
      Has Code

    Michael,

    I'm getting the same behavior as Andrew.  When the screen renders. the switch is always set to the default position regardless of contentItem.value being passed to setFlipSwitchValue.  The contentItem is being updated as is the database when you click save, but the switch is not diplaying the correct value initially.  Debugging shows setFlipSwitchValue succeeds, but the switch doesn't change visually if you close and open the screen again.

    I noticed your code doesn't pass newValue in the dataBind.

    I changed this:

    contentItem.dataBind('value', setFlipSwitchValue);

    to this:

            contentItem.dataBind('value', function (newValue) {
                setFlipSwitchValue(newValue);
            });

    But that did not help.  I wonder id $flipSwitch is losing scope (?)

    Josh


    • Edited by joshbooker Wednesday, January 30, 2013 8:51 PM
    •  
  • Wednesday, January 30, 2013 9:32 PM
     
     Proposed Has Code

    The your jQM link says this "If you manipulate a slider via JavaScript, you must call the refresh method on it to update the visual styling"  Funny how a reffresh is not required for the Range Slider but is for the flip switch.

    This worked for me:

     function setFlipSwitchValue(value) {
                $flipSwitch.val((value) ? 'true' : 'false');
    //init slider            
    $flipSwitch.slider();
    //refresh slider
                $flipSwitch.slider('refresh');
                // Because the flip switch has no concept of a "null" value
                //    (or anything other than true/false), ensure that the 
                //    contentItem's value is in sync with the visual representation
                contentItem.value = ($flipSwitch.val() === 'true');
            }

    Not sure why you have to init slider again since jQM should have doen that allready, but I got errors without it. 
    HTH,

    Josh

    • Proposed As Answer by joshbooker Wednesday, January 30, 2013 9:45 PM
    •  
  • Wednesday, January 30, 2013 9:42 PM
    Moderator
     
     Answered Has Code

    My apologies, I had mis-remembered in my response to Andrew above.  Looks like the refresh is required.  I will update the blog post momentarily.

    The fix is to add the following line to the helper setFlipSwitchValue function:  

    $flipSwitch.slider('refresh');

    Thus:

            function setFlipSwitchValue(value) {
                $flipSwitch.val((value) ? 'true' : 'false');
    
                // Because the flip switch has no concept of a "null" value
                //    (or anything other than true/false), ensure that the 
                //    contentItem's value is in sync with the visual representation
                contentItem.value = ($flipSwitch.val() === 'true');
    
                $flipSwitch.slider('refresh');
            }



    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office


  • Wednesday, January 30, 2013 9:47 PM
    Moderator
     
     

    Good catch, Josh.  I'll update the blog.  

    Actually, you don't even need to do the slider initialization, that should be handled automatically.  The refresh line is all you need.


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

  • Wednesday, January 30, 2013 9:54 PM
     
      Has Code

    For some reason I got jQuery errors about calling refresh method before initialization.    Those went away after adding this before refresh.

    $flipSwitch.slider();

    Can you explain how the newValue is received when you don't pass it to setFlipSwitchValue in the dataBind?

    Thanks,

    Josh

  • Wednesday, January 30, 2013 10:08 PM
    Moderator
     
      Has Code

    The fact that you got errors before calling refresh, and I did not, is very strange.  The control should be auto-initializing as per jQueryDocumentation, and because we're grabbing the flipSwitch variable in the setTimeout, it should, by that point, be a full-fledged slider.  Are you sure you are doing a setTimeout?  E.g., could you try this code in its entirety:

    function createBooleanSwitch(element, contentItem, trueText, falseText, optionalWidth) {
        var $selectElement = $('<select data-role="slider"></select>').appendTo($(element));
        $('<option value="false">' + falseText + '</option>').appendTo($selectElement);
        $('<option value="true">' + trueText + '</option>').appendTo($selectElement);
    
        // Now, after jQueryMobile has had a chance to process the 
        //     new DOM addition, perform our own post-processing:
        setTimeout(function () {
            var $flipSwitch = $('select', $(element));
    
            // Set the initial value (using helper function below):
            setFlipSwitchValue(contentItem.value);
    
            // If the content item changes (perhaps due to another control being
            //     bound to the same content item, or if a change occurs programmatically), 
            //     update the visual representation of the control:
            contentItem.dataBind('value', setFlipSwitchValue);
    
            // Conversely, whenver the user adjusts the flip-switch visually,
            //     update the underlying content item:
            $flipSwitch.change(function () {
                contentItem.value = ($flipSwitch.val() === 'true');
            });
    
            // To set the width of the slider to something different than the default,
            //    need to adjust the *generated* div that gets created right next to
            //    the original select element.  DOM Explorer (F12 tools) is a big help here.
            if (optionalWidth != null) {
                $('.ui-slider-switch', $(element)).css('width', optionalWidth);
            }
    
            //===============================================================//
    
            // Helper function to set the value of the flip-switch
            //     (used both during initialization, and for data-binding)
            function setFlipSwitchValue(value) {
                $flipSwitch.val((value) ? 'true' : 'false');
    
                // Having updated the DOM value, refresh the visual representation as well
                //     (required for a slider control, as per jQueryMobile's documentation)
                $flipSwitch.slider('refresh');
    
                // Because the flip switch has no concept of a "null" value
                //    (or anything other than true/false), ensure that the 
                //    contentItem's value is in sync with the visual representation
                contentItem.value = ($flipSwitch.val() === 'true');
            }
        }, 0);

    Regarding how newValue is received by the faction, there's no black magic there.  It's the moral equivalent of passing a pointer to a function.  Because dataBind expects a function that will take a value paramater, I don't need to create a function that just passes the same parameter to a different function -- I can just pass a pointer.  This is just JavaScript behavior, though there is a similar concept in .NET (creating a lambda function in LINQ or for an event handler, versus pointing to an existing function).

    Hope this helps,

    - Michael


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

  • Wednesday, January 30, 2013 10:11 PM
     
     

    Hi Michael,

    That is the point. Refresh method works as expected.

    Thanks a lot,

    Andrew

  • Wednesday, January 30, 2013 10:57 PM
     
      Has Code

    With your code above, I get this:

    Unhandled exception at line 1007, column 6 in http://ajax.aspnetcdn.com/ajax/jquery.mobile/1.1.1/jquery.mobile-1.1.1.js

    0x800a139e - Microsoft JScript runtime error: cannot call methods on slider prior to initialization; attempted to call method 'refresh'

    One diff from your post is that I'm using the code in a dialog window rather than your Add/Edit Screen.  This happens in some cases upon showDialog and other times not.  Seems like every other time I open a dialog.  The only other diff worth noting is that my showDialog is in a callback from the bing maps blog post:

                $(mapDiv).lightswitchBingMapsControl("addPinAsync", account.BusinessAddress,
                    "", "", i + 1, function () {
                        screen.Accounts.selectedItem = account;
                        screen.showDialog("Details");
                    });

    Thanks for the info on dataBind.  There's little documentation and I've seen it mostly wrapped in a function in the examples.  Is it true to say the first parameter is the property name that dataBind will resolve based on context of contentItem and then pass into the function specified in the second parameter?

    Josh

  • Wednesday, January 30, 2013 11:14 PM
    Moderator
     
     

    The fact you're doing a showDialog might be the reason.  In that case, the initialization makes sense, and should do no harm even when the control is already initialized.

    Regarding dataBind, yes, the callback function accepts a single parameter, the value.  For more details, see Joe's custom control and databindings post at http://blogs.msdn.com/b/lightswitch/archive/2012/12/06/custom-controls-and-data-binding-in-the-lightswitch-html-client-joe-binder.aspx.  You can search for the word "dataBind" to see the relevant parts.


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office


  • Thursday, January 31, 2013 12:59 AM
     
     

    Regarding dataBind, yes, the callback function accepts a single parameter, the value.  For more details, see Joe's custom control and databindings post at http://blogs.msdn.com/b/lightswitch/archive/2012/12/06/custom-controls-and-data-binding-in-the-lightswitch-html-client-joe-binder.aspx.  You can search for the word "dataBind" to see the relevant parts.


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office


    Also see:

    Writing JavaScript That Implements The Binding Pattern In Visual Studio LightSwitch

    Using Promises In Visual Studio LightSwitch

    The Visual Studio LightSwitch Marketplace

    http://LightSwitchHelpWebsite.com

  • Thursday, January 31, 2013 6:53 PM
     
      Has Code

    Andrew & Michael Z,

    It seems you need the refresh for the range slider also like so:

    function createSlider(element, contentItem, min, max) {
        // Generate the input element.
        //var v = ;
        //if (v == 0 || v == null) {v=(min + max)/2;}
        $(element).append('<input type="range" step="0.01" min="' + min +
            '" max="' + max + '" value="' + contentItem.value + '" />');
    
        // Now, after jQueryMobile has had a chance to process the 
        //     new DOM addition, perform our own post-processing:
        setTimeout(function () {
            var $slider = $('input', $(element));
    
            // If the content item changes (perhaps due to another control being
            //     bound to the same content item, or if a change occurs programmatically), 
            //     update the visual representation of the slider:
            contentItem.dataBind('value', function (newValue) {
                $slider.val(newValue);
            });
    
            // Conversely, whenever the user adjusts the slider visually,
            //     update the underlying content item:
            $slider.change(function () {
                contentItem.value = $slider.val();
            });
    //refresh display visuals
            $slider.slider("refresh");
        }, 0);
    };

    Otherwise screens open with the slider in the min position visually even though the data value is greater than min.

    HTH,

    Josh

  • Thursday, January 31, 2013 7:34 PM
    Moderator
     
     

    Hi Josh,

    Regarding the slider, I don't think you need the refresh.  The way the slider function works, contentItem.value is generated in the input element during the initial jQuery item creation.  As such, it should be initialized to the right value immediately, with no extra refresh necessary.  I just re-tried the code I have in the blog post above, and the slider is working properly (loading the right value) as is.


    Michael Zlatkovsky | Program Manager, Visual Studio Tools for Office & Apps for Office

  • Thursday, January 31, 2013 7:58 PM
     
     
    Sorry you're right, I tried again without refresh and it's working, oops.  Guess my eyes we're deceiving me.