locked
Html client: is it possible to use "declarative data binding" in an html custom control? RRS feed

  • Question

  • Following basic example shows how databinding can work in an html custom control.

    myapp.BrowseCustomers.RowTemplate_render = function (element, contentItem) {
        var firstName = $("<p>" + contentItem.value.FirstName + "</p>");
        firstName.appendTo($(element));
        contentItem.dataBind("value.FirstName", function (newValue) { firstName.text(newValue); });
    };

    Nice, it works well, but I was wondering if the databinding can not be modeled by means of html attributes solely (so without code), in such a way more elaborated custom controls can be stored in separate template files containing only html and no code?


    paul van bladel

    Sunday, January 26, 2014 9:36 AM

Answers

  • WinJS provides one-time and one-way binding.  Two-way binding is possible either imperatively by attaching to a specific event (onchange, onclick, etc.) or declaratively using a generic WinJS helper function.

    myapp.AddEditCustomer.created = function (screen) {
        // Write code here.
        WinJS.Namespace.define("Binding.Mode", {
            twoway: WinJS.Binding.initializer(function (source, sourceProps, target, targetProps) {
                WinJS.Binding.defaultBind(source, sourceProps, target, targetProps);
                target.onpropertychange = function () {
                    var targetValue = target[targetProps[0]];
                    source[sourceProps[0]] = targetValue;
                }
            })
        });
    };
    

    This helper function is coded once in the application; here it is placed in the created method of the AddEditCustomer screen.  Defining a WinJS Namespace has the advantage of avoiding global variable usage and their potential consequences, which your code relies upon. The generic WinJS.Binding.initializer takes as its arguments the current values of all the binding source and target properties, which is then passed to the standard WinJS default binding function, which subsequently watches for any property change event, keeping the source and target equal when raised.

    So, two-way binding between a custom control input textbox and a Customer contact name becomes:

    myapp.AddEditCustomer.Customer_render = function (element, contentItem) {
        var boundTextControl = $('<input type="text" data-win-bind="value: ContactName Binding.Mode.twoway" />');
        $(element).append(boundTextControl);
        WinJS.Binding.processAll(element, contentItem.screen.Customer);
    };
    


    Likewise, you could use a local screen property, for instance String and Phone Number, and in the postRender method, add the data-win-bind attribute (this would be very similar to your data-ls-bind example), and achieve two-way binding.

    myapp.AddEditCustomer.BoundCustomerName_postRender = function (element, contentItem) {
        // Write code here.
        $('input', $(element)).attr('data-win-bind', 'value: ContactName Binding.Mode.twoway');
        WinJS.Binding.processAll(element, contentItem.screen.Customer);
    
    };
    
    myapp.AddEditCustomer.BoundPhone_postRender = function (element, contentItem) {
        // Write code here.
        $('input', $(element)).attr('data-win-bind', 'value: Phone Binding.Mode.twoway');
        WinJS.Binding.processAll(element, contentItem.screen.Customer);
    
    };
    

    You can also bind a local screen property (in this case, screen.Range, an integer) to a custom control of a different type, such as a slider (note that jQueryMobile mark-up is disabled in this case):

    myapp.AddEditCustomer.Customer2_render = function (element, contentItem) {
        // Write code here.
        var slider = $('<input type="range" data-role="none" data-win-bind="value: Range Binding.Mode.twoway" />');
        $(element).append(slider);
        WinJS.Binding.processAll(element, contentItem.screen);
    };

    • Proposed as answer by joshbooker Sunday, May 4, 2014 12:35 AM
    • Marked as answer by Paul Van Bladel Sunday, May 4, 2014 7:12 AM
    Saturday, May 3, 2014 5:45 AM
  • Hey Paul,

    I wish it did, the current render&postrender methods don't feel as clean as the MVVM separation in the SL client.  Maybe that's on purpose, because people were always asking about 'getting a particular control' etc.

    Anyways, maybe you could try to get KnockoutJS or a similar library to play nicely with the LightSwitch client.  KnockoutJS offers all the declarative binding you need.  

    - Make sure the data-bind attributes 'survive' the JQueryMobile 'rendering' though.

    - Make sure you set the screen as the datacontext ('activating Knockout')

    Just an idea though... ;)

    Keep rocking LS!

    Jan


    It's your story - time to switch on the innovation.||About me||LightSwitch blog

    Sunday, January 26, 2014 2:28 PM

All replies

  • Using the JavaScript eval function you can execute any JavaScript from pure text such as this:

                // Get Summary Property
                var entityType = entity.details.getModel();
                var summaryAttribute = getAttributeForEntity(entityType, ':@SummaryProperty');
                var SummaryProperty = summaryAttribute.property.name;
    
                var shortText = eval('entity.' + SummaryProperty);

    However, for maximum performance (eval can be slow there are alternatives), I recommend creating LightSwitch templates that create explicit JavaScript:

    Creating Advanced LightSwitch HTML Screen Templates

    In my opinion this is a feature that works because of Visual Studio and it is something that no other JavaScript framework that I know of has.

    Yes I know that right now we only have screen templates, but I am able to use the screen templates I created in the link above, create the code in less than a minute that would have taken me an hour, and then cut and paste it into my existing page.


    Unleash the Power - Get the LightSwitch HTML Client book

    http://LightSwitchHelpWebsite.com

    Sunday, January 26, 2014 1:27 PM
  • Hey Paul,

    I wish it did, the current render&postrender methods don't feel as clean as the MVVM separation in the SL client.  Maybe that's on purpose, because people were always asking about 'getting a particular control' etc.

    Anyways, maybe you could try to get KnockoutJS or a similar library to play nicely with the LightSwitch client.  KnockoutJS offers all the declarative binding you need.  

    - Make sure the data-bind attributes 'survive' the JQueryMobile 'rendering' though.

    - Make sure you set the screen as the datacontext ('activating Knockout')

    Just an idea though... ;)

    Keep rocking LS!

    Jan


    It's your story - time to switch on the innovation.||About me||LightSwitch blog

    Sunday, January 26, 2014 2:28 PM
  • Hi Guys,

    Looking at msls.js, it may be possible by using data attributes with the prefix 'data-ls-'

    The following is a function in msls.js which appears to parse attribs for use in settting bindings for custom controls:

        function parseAttributes(resources, item, bindings, target, removeAttributes) {
    
            var attr,
                i,
                binding,
                isHtmlAttr,
                isBuiltInAttr,
                attributesToRemove = [];
    
            for (i = 0; i < item.attributes.length; i++) {
                attr = item.attributes[i];
    
                if (attr.name.indexOf("data-ls-") === 0) {
                    binding = setControlProperty(resources, target, attr.value);
                    if (binding) {
                        bindings.push(binding);
                    }
    
                    if (removeAttributes) {
                        attributesToRemove.push(attr.name);
                    }
                }
            }

    Another snip:

                    controlBindings = [];
                    parseAttributes(resources, item, controlBindings, subControl, false);
    
                    dataContextBinding = msls_iterate(controlBindings).first(function (b) {
                        return b.targetProperty === "data" && b.bindingTarget === subControl;
                    });
                    if (!dataContextBinding) {
                        dataContextBinding = setControlProperty(resources, subControl, "data:{data, bindingMode=[msls.data.DataBindingMode.oneWayFromSource]}");
                    }
    
                    if (dataContextBinding) {
                        dataContextBinding.bindingSource = owner;
                        dataContextBinding.bind();
                        controlBindings.push(dataContextBinding);
                    } else {
                    }

    I could be wrong, but how would I know since there is no JavaScript API reference for HTML Client

    Josh

    Wednesday, January 29, 2014 6:53 PM
  • Have you experimented with AngularJS and LightSwitch?  AngularJS directives would be the best candidate I know of for declarative data binding, assuming it plays well with LightSwitch.
    Thursday, January 30, 2014 2:41 AM
  • I'm revisiting this topic... 

    I tried Angular but LS does not like you to add the ng-app attribute to the main html element in default.htm and the LS app stops loading as a consequence.

    I then tried Knockout and it does seem to play nicely with LS, except I cannot figure out why it would not display a value in a textbox (on screen) whilst Knockout does indeed populate the textbox as expected and can be seen when inspecting the DOM. 

    I tried this:

    1. Created a LS HTML project connected to Northwind
    2. Create Customer browse and edit screens using all the defaults
    3. Added a custom control below all the other controls on the edit screen and set the DataContext to be the Customer
    4. In the custom control render method I added the following: 

    var textControl = "<input data-bind='text: CompanyName' type='text' style='width:300px;' data-role='none'></input>";
    $(textControl).appendTo($(element));
    ko.applyBindings(contentItem.value);

    If you run the app and open the edit screen you will notice that custom control textbox is rendered but is empty. If you inspect the DOM you will indeed see that the CompanyName value was correctly populated into the textbox by KO.

    This example is only one-way binding so far, but it should work. Any idea why it is not displaying the value in the textbox?

    Regards, Xander. My Blog

    • Edited by novascape Monday, April 28, 2014 11:38 PM
    Monday, April 28, 2014 11:28 PM
  • Hey Novascape!

    God I hope you get Knockout to play with LS mobile!  Wondering how you will cope with not having KO observable properties though.

    As per your current problem: I'm taking a wild guess, but the problem might be the JQM rendering.  If it is, then this article should be helpful.

    If not, then I have no idea ;)

    Keep rocking KO!

    Jan


    It's your story - time to switch on the innovation.||About me||LightSwitch blog

    Monday, April 28, 2014 11:40 PM
  • Hey Jan, thanks for the link, I will look into that.

    Btw, if I use a simple <span/> tag instead of the <input/> control then it displays correctly, so we know that KO is trying to play nicely!

    If we can get this to work, the possibilities are endless.


    Regards, Xander. My Blog

    Monday, April 28, 2014 11:44 PM
  • Hey Jan, thanks for the link, I will look into that.

    Btw, if I use a simple <span/> tag instead of the <input/> control then it displays correctly, so we know that KO is trying to play nicely!

    If we can get this to work, the possibilities are endless.


    Regards, Xander. My Blog

    Good stuff! Why don't these forums have a 'Like' button yet?

    If it works with a span and not an input, it's almost certainly due to the JQM rendering.  If I encounter this, I often cheat by putting my entire code in a 'SetTimeOut' function. As a simple test, you could do the same and see if your binding works? 


    It's your story - time to switch on the innovation.||About me||LightSwitch blog

    Monday, April 28, 2014 11:50 PM
  • I already tried the setTimeout() trick but no luck.

    If you inspect the DOM of a standard LS rendered textbox that contains a value, you cannot see that value in the DOM (well I can't find it anyway) and it certainly does not lie here <input type="text">[nothing here]</input>. Using my approach above the value in inserted like this <input type="text">[value put here]</input> and I suspect that is the difference and reason why it is not working.

     


    Regards, Xander. My Blog

    Monday, April 28, 2014 11:55 PM
  • If one does the data binding as per the OP and as per the Two Way Binding section in this article Custom Controls and Data Binding in the LightSwitch HTML Client (Joe Binder) then it works just fine with a textbox.

    However, then you face the issue of validation messages. I've not seen an article that quite explains how to handle custom control validation messages using the "Validation Rendering" -> "Custom" setting, but there might be a way to do it using that mechanism.

    Back to square one.


    Regards, Xander. My Blog

    Tuesday, April 29, 2014 12:38 AM
  • Not sure about the "Validation Rendering" -> "Custom" setting

    but you can do custom validation in HTML Client using either the beforeApplyChanges Screen Method like this:

    http://msdn.microsoft.com/en-us/library/jj733572.aspx#validate

     or by addChangeListener to the respective property like so:

    myapp.OrderDetail.created = function (screen) { 
      // Write code here. 
      screen.Order.addChangeListener("OrderDate", function (e) { 
        var order = screen.Order, contentItem = screen.findContentItem("OrderDate"), 
        today = new Date(); 
        today.setHours(0, 0, 0, 0); 
        if (order.details.entityState === msls.EntityState.added && 
            contentItem.validationResults.length === 0 && 
            order.OrderDate < today) { 
              contentItem.validationResults = [ 
                 new msls.ValidationResult( order.details.properties.OrderDate, 
                                            "Cannot be before today.") ]; 
        } 
      }); 
    }; 
    http://blogs.msdn.com/b/lightswitch/archive/2012/11/13/new-lightswitch-html-client-apis.aspx

    HTH,

    Josh

    Tuesday, April 29, 2014 1:14 AM
  • Thanks Josh, I know about the beforeApplyChanges() event, but your addChangeListener() example above gave me a hint.

    I was trying to find a way to inspect the contentItem.validationResults inside the custom control render method when automatic validation fails (e.g. CompanyName is required in the case of Customer.CompanyName).

    However, the way to perhaps do this is to add the addChangeListener() inside the custom render method and to validate the value and update your custom control right at that point with the validation error message. This could then become part of your custom control rendering library.

    I've tested this with a simple console.log() when there is a validation error and that works.

    So I think the conclusion of my experimentation this morning is the following:

    1. Knockout binding works fine inside LS for display-only fields.

    2. When you need entry fields (e.g. textboxes) it is best to stay with the LS built-in binding as per the OP and the above article.

    3. When you want to display custom validation messages next to (or inside) the custom control, you have to add a change listener to the contentItem.value (or contentItem.value.CompanyName in the above example), validate the value inside that listener and display the error message right then and there

    So quite a bit of work is required to create a custom rendering library that handles validation messages as well, but it is doable. I have to question myself whether the juice is worth the squeeze though?

    The two business cases I have for this are:

    1. I have entities that need to be edited in multiple locations and ideally it should be part of the underlying screen rather than using a popup modal

    2. I have a need for totally dynamic forms based based on a json object from the server - these are OK to be done in a popup modal.



    Regards, Xander. My Blog

    • Edited by novascape Tuesday, April 29, 2014 2:22 AM
    Tuesday, April 29, 2014 2:21 AM
  • A fantastic enhancement to LS (in my opinion) would be if we could create a normal screen in LS and mark that screen as a "re-usable control" and then be able to select that screen as a "control" inside another screen. The host screen would then provide the data context for the control/hosted screen.

    It would sort of offer what we had with "business types" in the old Silverlight days, but be more powerful. 

    I could create an address edit screen/control and embed it everywhere else in my app that an address needs to be edited.


    Regards, Xander. My Blog

    Tuesday, April 29, 2014 2:31 AM
  • Haven't used KO, but you might try binding the 'stringValue' attribute which, I think LS uses instead of 'text' in part to handle the diff between placeholdertext and input val.  NOt sure if it's present from a custom text control, but just a thought.

    Maybe like this:

    var textControl = "<input data-bind='stringValue: CompanyName' type='text' style='width:300px;' data-role='none'></input>";
    $(textControl).appendTo($(element));
    ko.applyBindings(contentItem.value);

    PS.. I like your idea about reusable nested screens...reminds me of subforms in MSAccess.  I'd add that we need a way to inherit from the msls.ui.controls so we can enhance the built in controls and make new ones using the same _fillTemplate, _attrachView, _customVisualStateHandler, etc. pattern as in msls.js.

    The fact that msls objects are independent anonymous-self-executing-functions makes this impossible.  This would require exposing a lot more objects from msls namespace.  Heck, why not expose it all? 

    Josh

    Tuesday, April 29, 2014 11:03 AM
  • Substituting WinJS 2.0 for the standard WinJS v1.0 allows for declarative data-binding without the need to add another library such as Knockout.

    Following your example above (#1-4), I will see a screen with a tile list of all the Northwind customers, along with an input textbox pre-filled with the first customers name Maria Anders.

    myapp.BrowseCustomers.ScreenContent_render = function (element, contentItem) {
        // Write code here.
        contentItem.screen.Customers.selectedItem = contentItem.screen.Customers.data[0];
    
        var textControl = $('<input type="text" data-win-bind="value: ContactName" />');
        $(element).append(textControl);
        WinJS.Binding.processAll(element, contentItem.screen.Customers.selectedItem);  // similar to ko.applyBindings()
    };

    • Proposed as answer by ADefwebserver Friday, May 2, 2014 1:02 AM
    Friday, May 2, 2014 12:43 AM
  • Neat, how'd you get the winjs-2.0.js file?  Everything I find is win 8 apps and it's broken up into multiple files:

        <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
        <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>

    TIA,

    Josh

    Friday, May 2, 2014 2:32 PM
  • I tried winjs2.0 and found it was doing one-time, one-way binding behavior. 

    I made little helper function which enables declarative binding for simple binding paths with simple validation.

    You'd call it in your Northwind example like this:

    myapp.AddEditCustomer.Customer_render = function (element, contentItem) {
        // Write code here.
        newControl = RenderBoundControl("<input type="text" data-ls-bind="ContactName">", element, contentItem,
            function (newValue) { return newValue.length > 3 }
            );
    };
    

    Find the code here:

    http://joshuabooker.com/Blog/Post/11/Declarative-Binding-

    HTH,

    Josh

    Friday, May 2, 2014 9:31 PM
  • WinJS provides one-time and one-way binding.  Two-way binding is possible either imperatively by attaching to a specific event (onchange, onclick, etc.) or declaratively using a generic WinJS helper function.

    myapp.AddEditCustomer.created = function (screen) {
        // Write code here.
        WinJS.Namespace.define("Binding.Mode", {
            twoway: WinJS.Binding.initializer(function (source, sourceProps, target, targetProps) {
                WinJS.Binding.defaultBind(source, sourceProps, target, targetProps);
                target.onpropertychange = function () {
                    var targetValue = target[targetProps[0]];
                    source[sourceProps[0]] = targetValue;
                }
            })
        });
    };
    

    This helper function is coded once in the application; here it is placed in the created method of the AddEditCustomer screen.  Defining a WinJS Namespace has the advantage of avoiding global variable usage and their potential consequences, which your code relies upon. The generic WinJS.Binding.initializer takes as its arguments the current values of all the binding source and target properties, which is then passed to the standard WinJS default binding function, which subsequently watches for any property change event, keeping the source and target equal when raised.

    So, two-way binding between a custom control input textbox and a Customer contact name becomes:

    myapp.AddEditCustomer.Customer_render = function (element, contentItem) {
        var boundTextControl = $('<input type="text" data-win-bind="value: ContactName Binding.Mode.twoway" />');
        $(element).append(boundTextControl);
        WinJS.Binding.processAll(element, contentItem.screen.Customer);
    };
    


    Likewise, you could use a local screen property, for instance String and Phone Number, and in the postRender method, add the data-win-bind attribute (this would be very similar to your data-ls-bind example), and achieve two-way binding.

    myapp.AddEditCustomer.BoundCustomerName_postRender = function (element, contentItem) {
        // Write code here.
        $('input', $(element)).attr('data-win-bind', 'value: ContactName Binding.Mode.twoway');
        WinJS.Binding.processAll(element, contentItem.screen.Customer);
    
    };
    
    myapp.AddEditCustomer.BoundPhone_postRender = function (element, contentItem) {
        // Write code here.
        $('input', $(element)).attr('data-win-bind', 'value: Phone Binding.Mode.twoway');
        WinJS.Binding.processAll(element, contentItem.screen.Customer);
    
    };
    

    You can also bind a local screen property (in this case, screen.Range, an integer) to a custom control of a different type, such as a slider (note that jQueryMobile mark-up is disabled in this case):

    myapp.AddEditCustomer.Customer2_render = function (element, contentItem) {
        // Write code here.
        var slider = $('<input type="range" data-role="none" data-win-bind="value: Range Binding.Mode.twoway" />');
        $(element).append(slider);
        WinJS.Binding.processAll(element, contentItem.screen);
    };

    • Proposed as answer by joshbooker Sunday, May 4, 2014 12:35 AM
    • Marked as answer by Paul Van Bladel Sunday, May 4, 2014 7:12 AM
    Saturday, May 3, 2014 5:45 AM
  • Very nice. Paul should mark this as answer....and unmarked those done by moderator. Thanks for this.
    Sunday, May 4, 2014 12:35 AM
  • Thanks, but Paul's question was actually a stealth two-parter, asking about declarative data-binding and how to do it:

    "...in such a way more elaborated custom controls can be stored in separate template files containing only html and no code?"

    So the question is half-answered above.  In the future, I'll try to post a code example using the WinJS.Binding.Template method to show how to render re-usable mark-up from external HTML files, which obviates the need for unwieldy, tedious jQuery DOM manipulation. This technique is conceptually similar to AngularJS' ng-include directive, or ASP.NET MVC partial views, for anyone familiar with these.

    Sunday, May 4, 2014 2:10 PM
  • Thanks for sharing your great insights !

    paul.


    paul van bladel

    Sunday, May 4, 2014 2:14 PM
  • May that future be swift and prosperous!

    This post is probably the best stuff about the HTML client I've read since it's announcement :-D

     

    It's your story - time to switch on the innovation.||About me||LightSwitch blog

    Sunday, May 4, 2014 2:29 PM
  • Thanks, but Paul's question was actually a stealth two-parter, asking about declarative data-binding and how to do it:

    "...in such a way more elaborated custom controls can be stored in separate template files containing only html and no code?"

    So the question is half-answered above.

    Yes but half-answered is better than those marked by moderator which were not even proposed as answers. 

    Thanks again for this.  The LS community really needs a top notch javascript guy providing great stuff like this.

    Here is a post that describes WinJS two-way binding with intitializer, etc. 

    http://skimedic.com/blog/post/2013/02/22/MVVM-in-WinJS-Part-3-e28093-Binding-Initializers-and-Two-Way-Binding.aspx

    I could be wrong, but it looks like he's doing it with WinJS 1.0.  Curious, do we have to switch to 2.0 for this to work?

    Also curious whether LS will be completely happy since, in lieu of WinJS binding, LS appears to have it's own dependency chaining built into msls.data.DataBinding. ( addLifeTimeDependency ,Etc.)

    Thanks again.


    • Edited by joshbooker Sunday, May 4, 2014 2:46 PM
    Sunday, May 4, 2014 2:33 PM
  • Those are all good questions, Josh.  I have only begun recently to experiment with WinJS 2.0 since it became open source last month at //build.  I do not have any LightSwitch HTML apps in production (yet) with incorporated WinJS 2.0, and of course anyone who tries these code suggestions should thoroughly apply testing in their own applications before deploying. One goal of mine in posting these samples is to encourage developers smarter than me (read: all of you guys seeing this) into possibly experimenting on their own, then sharing their discoveries here on the forum. 

    The WinJS 1.0 included in standard LightSwitch HTML is only a fragment of the full WinJS library used in Windows 8 apps; in particular, the WinJS.Binding namespace, as well as the WinJS.UI namespace, are not included, and so you could not use the code in the blog listing in a standard LightSwitch app without switching.

    Anyhow, now on to part two of declarative data binding using templates.

    Using jQuery or JavaScript to add mark-up to the DOM is relatively straightforward when simple nodes are required, but becomes a nightmare when complex custom controls with multiple nodes and styling are required:

                // Create a JQuery Moble container
                var objFieldcontain = $("<div data-role='fieldcontain' />");
                var objFieldset = $("<fieldset data-role='controlgroup' />");
                objFieldset.appendTo($(objFieldcontain));
                var CustomUl = $("<ul class='msls-listview ui-listview' data-role='listview' data-inset='false'></ul>");
                CustomUl.appendTo($(objFieldset));
                // Loop through each page
                $.each(colPages, function (index, paramPageContent) {
                    // Create Button text
                    var shortText = jQuery.trim(paramPageContent.PageContent)
                        .substring(0, 300).split(" ").slice(0, -1).join(" ") + "...";
                    var CustomDiv = "<li tabindex='0' class='ui-li ui-btn ui-btn-up-a ui-btn-up-undefined' ";
                    CustomDiv = CustomDiv + " data-msls='true' ";
                    CustomDiv = CustomDiv + "onclick='editPage(" + paramPageContent.PageId + "," + chapterId + "); ";
                    CustomDiv = CustomDiv + "return false' rel='external>";
                    CustomDiv = CustomDiv + "<div class='msls-presenter msls-list-child ";
                    CustomDiv = CustomDiv + "msls-ctl-summary msls-vauto msls-hauto ";
                    CustomDiv = CustomDiv + "msls-compact-padding msls-leaf msls-presenter-content ";
                    CustomDiv = CustomDiv + "msls-font-style-normal'>";
                    CustomDiv = CustomDiv + "<div class='msls-text-container'>";
                    CustomDiv = CustomDiv + "<span class='id-element'>" + shortText + "</span>";
                    CustomDiv = CustomDiv + "</div>";
                    CustomDiv = CustomDiv + "</div>";
                    CustomDiv = CustomDiv + "<div class='msls-clear'></div>";
                   CustomDiv = CustomDiv + "</li>";
                    // Create the Button
                    objButton[index] = $(CustomDiv);
                    // Add Div to the CustomUl
                    objButton[index].appendTo($(CustomUl));
                });
               
    
                // Add contaner to the element          
                objFieldset.appendTo($(element));
                // Tell JQuery Moble to render
                objFieldset.trigger("create");
            }
    

    It would be nicer if complex HTML could be kept in an external file and injected in the DOM en bloc, since many of us have become hooked on IntelliSense, automatic closing tags and indentations, Emmet, etc.  In this example, I switched to using the Amazing Pie database, since the oData-derived NorthWind database doesn't allow saving changes.  I'm going to use the WinJS UI custom control ListView to show a list of the Amazing Pie Food Items with the ability to edit the Name and Price properties.

    So, in a Browse Food Items screen that has an Edit behavior, I'll create a custom control of the Food Items list:

    /// <reference path="~/GeneratedArtifacts/viewModel.js" />
    /// <reference path="~/Scripts/ui.js" />
    /// <reference path="~/Scripts/base.js"/>
    
    myapp.BrowseFoodItems.FoodItems_render = function (element, contentItem) {
        // Write code here.
        element.innerHTML = '<div id="placeholder"></div>';
    
        contentItem.value.addChangeListener('isLoaded', function () {
            var listFoodItems = new WinJS.Binding.List(contentItem.screen.FoodItems.data);
    
            WinJS.Binding.Template.render('tmplFoodItems.html', listFoodItems, document.getElementById("placeholder"))
                .then(function() {
                    return WinJS.UI.processAll().done(function() {
                        var lvFoodItems = document.getElementById("lvFoodItems").winControl;
                        lvFoodItems.itemDataSource = listFoodItems.dataSource;
                    });
                });
        });
    };
    

    There is only one node in the custom control initially, a <div> with Id "placeholder."  In a separate HTML file called tmplFoodItems, I'll put the markup for the custom control.

    <style> #lvFoodItems .foodItem { border: solid 1px black; padding: 10px; width: 300px; } </style> <div id="tmplFoodItems" data-win-control="WinJS.Binding.Template"> <div class="foodItem win-interactive"> <input type="text" data-win-bind="value: Name Binding.Mode.TwoWay"/> Price: <input type="text" data-win-bind="value: Price Binding.Mode.TwoWay" /> </div> </div> <div id="lvFoodItems" data-win-control="WinJS.UI.ListView" data-win-options="{ itemTemplate: select('#tmplFoodItems'), layout: { type: WinJS.UI.GridLayout, maximumRowsOrColumns: 4, orientation: 'vertical' } }"> </div>


    The HTML file has a Template div ("#tmplFoodItems") containing input controls bound to the Name and Price of a food item, as well as a div ("#lvFoodItems") for the ListView control.  In this control, the ListView uses the #tmplFoodItems div as its template, so essentially you have a template within a template.

    Once LightSwitch has loaded the FoodItems visual collection, the WinJS datasource for the FoodItems listview is assigned to the variable listFoodItems.  Next, the WinJS Template render function is invoked, which takes three arguments: the URL of the template file, the data source, and the document element to render the template.  This is a promise object, so a promise chain is created in order to create the ListView template using WinJS.UI.processAll().  After WinJS renders the FoodItems listview, the data source of the listview is explicitly bound to the screen's visual collection as listFoodItems.dataSource.

    Here's a result:

    So, changing the Name or Price in any of the items in the ListView custom control updates the corresponding entity on the screen's visual collection (the bottom half of the screen shows LightSwitch's native Tile List for the Food Items) which can be saved to the database.  This seems particularly useful to me at least, since it has been difficult in the past to databind to an entire visual collection using a custom control.

    Tuesday, May 6, 2014 6:20 PM
  • That's very Cool.  Thanks!

    One goal of mine in posting these samples is to encourage developers smarter than me (read: all of you guys seeing this) into possibly experimenting on their own, then sharing their discoveries here on the forum. 

    While I'm definitely not smarter, I have been experimenting so I'll share.  There is a control in msls called ContentControl which does declarative binding.  msls uses it internally to render headers and commands bars and-the-like from html templates.  Some of the code for this control can be seen in my post above from Jan 29th.

    Two problems: 

    1) You cannot create this type of control in the designer and

    2) msls.ui.controls namespace is not 'exposed'.

    Well as an experiment, I exposed everything:

    http://joshuabooker.com/Blog/Post/12/msls-js-Exposed

    Then tried this, in a custom control bound to screen.Customer:

    myapp.AddEditCustomer.Customer_render = function (element, contentItem) {
        // Write code here.
        thatTemplate = '<div class="subControl" control="TextBox" data-ls-text="text:{data.ContactName}"</div>'
        thatControl = new msls.ui.controls.ContentControl($(element));
        thatControl.dataTemplate = thatTemplate;
        thatControl.data = contentItem.data;
        thatControl.render();
    };

    What do you know...it worked...I see 'Maria Anders' with two-way binding!

    It renders in the div.subControl a msls.ui.controls.TextBox with all the default props, bindings, etc.  Then it binds the text property to contentItem.data.ContactName. 

    More info here:

    http://joshuabooker.com/Blog/Post/13/msls-Exposed--Declarative-Binding

    I wish they'd expose more stuff, especially ui.controls.

    Thanks again for sharing,

    Josh



    • Edited by joshbooker Wednesday, May 7, 2014 11:18 AM more...
    Tuesday, May 6, 2014 6:56 PM