locked
WinMD and Databinding

    Question

  • If you have a View Model in C#, how do you reuse it in a JS/html5 application?  I have made the project compile as WinMD.  However, it doesn't actually compile for several reasons.

    1. is uses the INotifyPropertyChanged, etc interfaces so the controls with databinding see the updates,
    2. because of the issues where the INotifyCollectionChanged interface doesn't work there are base classes to fix that which are generics, 
    3. the base class of my classes is not Object or composable from WinRT classes (see #2) 
    4. you can't have abstract classes exported to WinRT
    5. (there are way more build errors, but you get the idea)

    Wow, this seems like a lot of restrictions to creating a WinMD component... and thus being able to reuse code across different C# implementations as well as JS/html5 applications.

    So how does the databinding work in JS/html5?  How are the controls told there are updates to the underlying data in the View Model?  Based on above, it doesn't seem that it works in any way like XAML based applications since you can't use any of the interfaces that make that work.

     

    Thanks,

      Mary-Ellen Chaffin

    Thursday, October 13, 2011 5:45 PM

Answers

  • Currently we don’t support WinJS binding against WinRT objects.
    Jeff Sanders (MSFT)
    Monday, October 17, 2011 7:46 PM
    Moderator

All replies

  • Hi Mary-Ellen,

    There are a bunch of good samples in the sample gallery to get you started.  Check them out and come back if you have some further questions!

    http://code.msdn.microsoft.com/windowsapps/Data-source-adapter-sample-3d32e535
    http://code.msdn.microsoft.com/windowsapps/Custom-data-sources-example-8a58a0fc
    http://code.msdn.microsoft.com/windowsapps/DeclarativeBinding-bfcb42a5

    -Jeff


    Jeff Sanders (MSFT)
    Thursday, October 13, 2011 6:16 PM
    Moderator
  • None of those samples use an external View Model to bind to. I have removed all of the offensive stuff so my simple view model builds as a WinMD.  Basically, I have a Movie class and a MovieCollection class.  They don't have any "notify" interfaces since those were offensive to WinMD and have no events at all. So it all compiles now.  I have also set a reference to my WinMD class library from my JS/html5 application.  But how do I actually refernence those classes?  How do I get to MyClassLib.Movie to be able to use it for the databinding?  Is there something like a "using MyClassLib;" like you'd have in a C# file?


    Thanks,

      Mary-Ellen

    Thursday, October 13, 2011 10:08 PM
  • I've got to be missing something here. 

    I just watched the http://channel9.msdn.com/Events/BUILD/BUILD2011/TOOL-531T video where they use a C# component from JS.  After they changed the C# to compile as WinMD and added the reference in their JS app, the intellisense just picked it up and saw their class.  However, mine doesn't do that.

    I type in "var movie = new " but then get no intellisense for my namespace and class. If I type it all in anyway, it still doesn't seem to see it.

     

    • Proposed as answer by Erez A. Korn Tuesday, January 8, 2013 7:50 AM
    • Unproposed as answer by Erez A. Korn Tuesday, January 8, 2013 7:50 AM
    Friday, October 14, 2011 12:59 AM
  • After closing and reopening VS and then also rebuilding the solution several times, I have been able to get it to see my WinMD objects. However, the databinding in JS blows up with an error stating "JavaScript runtime error: Cannot define property '_getObservable': object is not extensible". So I searched for that online and it said something about turning off 'use strict' so I commented that out... but I still get that error. Thanks, Mary Ellen
    Monday, October 17, 2011 7:14 PM
  • Currently we don’t support WinJS binding against WinRT objects.
    Jeff Sanders (MSFT)
    Monday, October 17, 2011 7:46 PM
    Moderator
  • Is binding WinRT objects in WinJS still unsupported?

    I was able to do so, but I had to use WinJS.Binding.oneTime. Since response is 7 months old, I was curious to see if there was any update on this.

    Thanks

    Wednesday, May 23, 2012 5:32 PM
  • While it appears that nothing has changed (Jeff is still correct) I wanted to share some work I did today around this to enable binding "directly" to C# objects. The short version is that it's setting up 2-way binding between a JS object and the C# object. I'll start with the code and then explain the key components.

    JavaScript

    var JSBindingBridge = WinJS.Class.mix(function (coreObj) {
        this._initObservable();
        var coreProps = WinJS.Binding.expandProperties(coreObj);
    
        //listen to changes from the core object (if it supports it)
        coreObj.addEventListener && coreObj.addEventListener("propertychanged", function(changeEvent) {
            var propname = changeEvent.detail[0].toLowerCase();
            for (var p in coreProps) {
                //WinRT doesn't allow properties/methods to differ only by case, so we're safe to do a case insensitive match to get around the casing rules in JavaScript
                if (p.toLowerCase() == propname && this[p] != coreObj[p])
                    this[p] = coreObj[p];
            }
        }.bind(this));
    
        //Initialize our object properties
        for (var prop in coreProps) {
            var corePropValue = coreObj[prop];
    
            //Add the property/method to the bridge object so it can be accessed directly, and change notifications work
            //  *we can actually add function calls, but they must be bound to the context of the core object for "this" to resolve properly
            this.addProperty(prop, corePropValue && corePropValue.bind ? corePropValue.bind(coreObj) : corePropValue);
    
            //Setup binding changes on this object to pass changes to the core object
            this.bind(prop, function (newValue) { coreObj[prop] = newValue; });
        }
    }, WinJS.Binding.mixin);

    C#

    //INotifyPropertyChanged doesn't project to WinRT, so we have to create our own
    public interface INotify
    {
        event TypedEventHandler<IObject, string> PropertyChanged;
    }
    
    public sealed class BindableBase
    {
        public event TypedEventHandler<IObject, string> PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            if (Dispatcher.HasThreadAccess)
            {
                if (PropertyChanged != null && !String.IsNullOrWhiteSpace(propertyName))
                    PropertyChanged(this, propertyName);
            }
            else
                Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => RaisePropertyChanged(propertyName));
        }
    
    }

    Alright, now for the details:

    1. In RaisePropertyChanged, bad things happen if you try to notify the JavaScript from a background thread. The code there won't compile until you figure out how to get a reference to your UI dispatcher. That is left as an exercise to the reader.
    2. The 2-way binding (really just 2 event handlers) is pretty clear in there, but pay close attention to the code "corePropValue.bind(coreobj)" - this enables us to call C# methods on the JavaScript object as though we're not even working with a wrapped object.

    Finally, an example of how to use it:

    //Left to the reader to actually get the C# object into the JavaScript
    var csharpVM = TODO;   //e.g. An object that has a title property and a method: "loadItems()"
    
    var observableVM = new JSBindingBridge(csharpVM);
    
    WinJS.Binding.processAll(document.querySelector("#pageRoot"), observableVM).then(function() {
        observableVM.loadItems();
    });

    I will highlight that while this is working in current code, I literally wrote it today and have thus not run it into production-level quality. There is also more that can be done to extend this with other mixins, but I won't go into those here.


    Adam


    Thursday, November 22, 2012 12:40 AM
  • Thanks for sharing with the community!

    Jeff Sanders (MSFT)

    Friday, November 23, 2012 9:25 PM
    Moderator
  • Glad to!

    I did find when adding it to production code that I introduced a noticeable hang on the UI thread. Profiling found that long-running code (in the C# properties) was being called unnecessarily.

    Looking back at the bridge object, a few major drawbacks appeared:

    1. All property getters for the C# object are called, no matter whether they are used or not. This breaks away from any lazy loading by design in the VM.
    2. A change delegate is wired up for all properties in order to pass changes back to the C#. (I'm not really sure the performance impact here)
    3. The object signature/shape is redefined for every use of a C# object. (Imagine returning a list of 100 items that each has 10 properties. That's 1,000 calls to Object.defineProperty)

    I took another look at the "WinJS.Binding.mixin" object and came up with a plan to at least address #1 and #2. The gist is to use the C# object as the backing data.

        var JSBindingBridge = WinJS.Class.mix(function (coreObj) {
            this._initObservable(coreObj);
            var coreProps = WinJS.Binding.expandProperties(coreObj);
    
            //listen to changes from the core object
            coreObj.addEventListener("propertychanged", function (changeEvent) {
                var propname = changeEvent.detail[0].toLowerCase();
                for (var p in coreProps) {
                    //WinRT doesn't allow properties/methods to differ only by case, so we're safe to do a case insensitive match to get around the casing rules in JavaScript
                    if (p.toLowerCase() == propname)
                        this.notify(p, this[p]);
                }
            }.bind(this));
    
            //Initialize our object properties
            for (var prop in coreProps) {
                //We could call "addProperty" but it takes in a default value and we want to lazily load the value from the core
                this.defineProperty(prop);
            }
        }, {
            defineProperty: function(name) {
                if (!this[name]) {
                    Object.defineProperty(this,
                        name, {
                            get: function () {
                                var propValue = this.getProperty(name);
                                //if "bind" is defined, it means the core property is a method, and we want to return it, called in the context of the core object
                                return propValue && propValue.bind ? propValue.bind(this._backingData) : propValue;
                            },
                            set: function(value) { this.setProperty(name, value); },
                            enumerable: true,
                            configurable: true
                        }
                    );
                }
            },
        }, WinJS.Binding.mixin);
    

    The keys here are:

    1. _initObservable takes in the C# object. Thus (with the mixin) all get/set operations actually operate directly on the C# object, both eliminating the need for an initial value and the binding listener to pass changes to the C#. (Change notifications are still necessary from the C# in order to notify the UI of changes)
    2. Adds defineProperty as a method that only defines the property signature on the JavaScript object. (addProperty takes and sets an initial value)

    Known remaining issues:

    1. The "WinJS.Binding.mixin" implementation of "getProperty" returns WinJS.Binding.as(val) of the value. If this returned value were to be a C# object (a child VM for example, rather than a string/number) then it should crash at that line. Code design aside, it may be necessary to redefine the mixin to support this scenario.
    2. As noted in #3 above, the object signature/shape is being defined on each call. I really don't know the performance implications here, but my best guess as I race out the door for the night is, if performance is an issue for you: create a mixin that consolidates the properties as defined in the C#, and mix it with the JSBindingBridge to create something like ObservableBook. I can post an example of what I'm thinking another time if necessary.

    Adam

    • Proposed as answer by adamhewitt627 Tuesday, November 27, 2012 4:08 PM
    • Unproposed as answer by adamhewitt627 Tuesday, November 27, 2012 7:39 PM
    Tuesday, November 27, 2012 1:04 AM
  • Alright, if I can resist the urge to be neurotic about continuing to work on the same code, then this should be my last post. I've left a couple TODO comments in that will be left to the reader (and myself as I continue to use this in development) but there isn't an immediately clear answer to me on them.

    With the shift to the C# object being the backing data of the binding, I ran into trouble with extending the JSBindingBridge with code like the following: ("source" needs binding notifications)

        var ObservableDocument = WinJS.Class.mix(JSBindingBridge, WinJS.Binding.expandProperties({
            key: null,
            group: null,
            source: null,
        }));
    

    In short, the collision came between JavaScript object backing (extensible) and C# object backing (non-extensible). My answer: mimic WinJS.Binding.mixin and split the backing data to a second object. (Contrast this with my first offering that set up a 2-way binding to a second object, this acts directly on the C# now that I have a better understanding of how the binding mixin works)

     

    A couple other additions:

    1. "coreBackingData" can be set multiple times on the same bridge, and it will reset its state (if I've actually written that properly. I don't yet have a scenario where this is necessary)
    2. Rather than being defined on a base object, the necessary code has been pulled out into a mixin that should be reasonably simple to include in other objects. (I haven't actually tried this part)
    var coreObjectMixin = {
        coreBackingData: {
            //We don't define a getter method because we want to force access through the properties we will define on the JavaScript object
            set: function (newData) {
                var oldData = this._coreBackingData;
                if (newData !== oldData) {
                    var oldProps = this._coreProperties;
    
                    //Cleanup values from the old data
                    if (oldData) {
                        //Remove core change listener
                        oldData.removeEventListener && oldData.removeEventListener("propertychanged", this.coreChangeHandler);
    
                        //Remove properties of the old data
                        //TODO - do we need to notify binding listeners?
                        for (var op in oldProps)
                            delete this[op];
                    }
    
                    if (newData) {
                        //Set values to our containers
                        this._coreBackingData = newData;
                        this._coreProperties = WinJS.Binding.expandProperties(newData);
    
                        //Listen to changes from the core object
                        newData.addEventListener && newData.addEventListener("propertychanged", this.coreChangeHandler);
    
                        var newProps = this._coreProperties;
                        //Initialize our object properties
                        //TODO - do we need to notify binding listeners?
                        for (var prop in newProps) {
                            this.defineProperty.call(this, prop);
                        }
                    } else {
                        this._coreBackingData = {};
                        this._coreProperties = {};
                    }
                }
            },
        },
    
        getCoreProperty: function (name) {
            var data = this._coreBackingData[name];
    
            //Functions must be called in the context of the backing object
            if (typeof data == 'function')
                return data.bind(this._coreBackingData);
    
            //TODO: The dynamicObservableMixin returns WinJS.Binding.as(data), but that would break if we return a C# object. 
            //  We need to return something bindable if it's not a value primitive.
            return data;
        },
    
        setCoreProperty: function (name, value) {
            var oldValue = this._coreBackingData[name];
            if (oldValue !== value) {
                this._coreBackingData[name] = value;
    
                //While the C# will also notify on this change, this is designed to be flexible enough for JavaScript objects as well
                return this.notify(name, value);
            }
            return WinJS.Promise.as();
        },
    
        defineProperty: function (name) {
            if (!this[name]) {
                Object.defineProperty(this,
                    name, {
                        get: function () { return this.getCoreProperty(name); },
                        set: function (value) { this.setCoreProperty(name, value); },
                        enumerable: true,
                        configurable: true
                    }
                );
            }
        },
    
        coreChangeHandler: {
            get: function () {
                //Define the C# property changed handler
                if (!this._coreChangeHandler) {
                    this._coreChangeHandler = function(changeEvent) {
                        var propname = changeEvent.detail[0].toLowerCase();
                        for (var p in this._coreProperties) {
                            //WinRT doesn't allow properties/methods to differ only by case, so we're safe to do a case insensitive match to get around the casing rules in JavaScript
                            if (p.toLowerCase() == propname) {
                                this.notify(p, this[p]);
                                break;
                            }
                        }
                    }.bind(this);
                }
                return this._coreChangeHandler;
            }
        },
    };
    
    
    var JSBindingBridge = WinJS.Class.mix(function (coreObj = null) {
        this._initObservable();
        this.coreBackingData = coreObj;
    }, WinJS.Binding.mixin, coreObjectMixin);
        
    

     


    Adam

    • Proposed as answer by adamhewitt627 Tuesday, November 27, 2012 8:01 PM
    Tuesday, November 27, 2012 8:01 PM
  • In the never-ending cycle of handling this, I have discovered that the "removeEventListener" doesn't actually unregister the handler? (I put a loop in the C# to continue to change a property, and called "removeEventListener" from within a setTimeout)

    It seems like WinRT is wrapping the JavaScript method in a C# object, which is then not the same wrapper as used by the addEventListener call.

    It does appear to work if you use: newData.onpropertychanged = this.coreChangeHandler and oldData.onpropertychanged = null


    Adam

    Wednesday, February 20, 2013 9:28 PM
  • Any chance you could put this solution out on github so we can use it and continue to improve it as a community?

    I'm on a project where we are using HTML/WinJS for the Views, but all our ViewModels are going to be in C# as Windows Runtime Components.  The goal being (in our case) to leverage our considerable C# talent from the ViewModels up, but use HTML/JS for the UI code.  We have a lot of existing code in C# that we want to migrate over into this new Windows Store app, but the UI *has* to be in HTML/JS.  


    • Edited by chrisaswain Friday, April 19, 2013 10:35 PM
    Friday, April 19, 2013 10:33 PM
  • Hi Adam,

    Sorry for being a bit dense here, but could you clarify the relationship between INotify, BindableBase and the view model classes?

    I'm not seeing an easy way to link these up at the moment, any info greatly appreciated.

    Friday, July 5, 2013 8:26 AM