Announcement

Collapse
No announcement yet.

Can Bridge customize class creation?

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

    Can Bridge customize class creation?

    Hello.

    First I have to say that I used nikhil kothari's Script# a lot of years ago (since the first public version), and even if I was very impressed by it, I couldn't customize it to my needs and stop using it.

    A few days ago I was ordered to do some openui5 development and I found Bridge.net. I'm trying to see if it can help me doing openui5 development in c#.

    openui5 extend classes this way:

    sap.ui.define([
            'jquery.sap.global',
            'sap/ui/core/mvc/Controller',
            'sap/ui/model/json/JSONModel'
        ], function(jQuery, Controller, JSONModel) {
        "use strict";
    
        return Controller.extend("sap.m.sample.TileContainer.Page", {
    
            onInit : function (evt) {
                // set mock model
                var sPath = jQuery.sap.getModulePath("sap.m.sample.TileContainer", "/data.json");
                var oModel = new JSONModel(sPath);
                this.getView().setModel(oModel);
            },
    
            handleEditPress : function (evt) {
                var oTileContainer = this.getView().byId("container");
                var newValue = !oTileContainer.getEditable();
                oTileContainer.setEditable(newValue);
                evt.getSource().setText(newValue ? "Done" : "Edit");
            },
    
            handleBusyPress : function (evt) {
                var oTileContainer = this.getView().byId("container");
                var newValue = !oTileContainer.getBusy();
                oTileContainer.setBusy(newValue);
                evt.getSource().setText(newValue ? "Done" : "Busy state");
            },
    
            handleTileDelete : function (evt) {
                var tile = evt.getParameter("tile");
                evt.getSource().removeTile(tile);
            }
        });
    });
    Using bridge with the current retyped.openui5 I've got this:

    Bridge.assembly("RTestLib", function ($asm, globals) {
        "use strict";
    
        Bridge.define("ClientControllers.LinkControllerSample", {
            inherits: [sap.ui.core.mvc.Controller],
            ctors: {
                ctor: function (sName) {
                    this.$initialize();
                    sap.ui.core.mvc.Controller.call(this, sName);
                }
            },
            methods: {
                onInit$1: function () {
                    // set mock model
                    var sPath = ""; // openui5.JquerySap.getModulePath("sap.m.sample.TileContainer", "/data.json");
                    var oModel = new sap.ui.model.json.JSONModel(sPath);
                    this.getView().setModel(oModel);
                },
                handleEditPress: function (evt) {
                    var oTileContainer = this.getView().byId("container");
                    //var newValue = !oTileContainer.getEditable();
                    //oTileContainer.setEditable(newValue);
                    //evt.getSource().setText(newValue ? "Done" : "Edit");
                },
                ehandleBusyPress: function () {
                    // set mock model
                    var sPath = ""; // openui5.JquerySap.getModulePath("sap.m.sample.TileContainer", "/data.json");
                    var oModel = new sap.ui.model.json.JSONModel(sPath);
                    this.getView().setModel(oModel);
                },
                handleTileDelete: function (evt) {
                    var tile = evt.getParameter("tile");
                    //evt.getSource().removeTile(tile);
                }
            }
        });
    });
    using this c# code:

    using Retyped;
    using Bridge;
    
    namespace ClientControllers
    {
        public class LinkControllerSample : openui5.sap.ui.core.mvc.Controller
        {
            public LinkControllerSample(Union<string, object[]> sName) : base(sName)
            {
            }
    
            public void onInit()
            {
                // set mock model
                var sPath = ""; // openui5.JquerySap.getModulePath("sap.m.sample.TileContainer", "/data.json");
                var oModel = new openui5.sap.ui.model.json.JSONModel(sPath);
                this.getView().setModel(oModel);
            }
    
            public void handleEditPress(openui5.sap.ui.@base.Event evt)
            {
                var oTileContainer = this.getView().byId("container");
                //var newValue = !oTileContainer.getEditable();
                //oTileContainer.setEditable(newValue);
                //evt.getSource().setText(newValue ? "Done" : "Edit");
            }
    
            public void ehandleBusyPress()
            {
                // set mock model
                var sPath = ""; // openui5.JquerySap.getModulePath("sap.m.sample.TileContainer", "/data.json");
                var oModel = new openui5.sap.ui.model.json.JSONModel(sPath);
                this.getView().setModel(oModel);
            }
    
            public void handleTileDelete(openui5.sap.ui.@base.Event evt)
            {
                var tile = evt.getParameter("tile");
                //evt.getSource().removeTile(tile);
            }
        }
    }
    I had to comment some lines as the current retyped.openui5 assembly needs some improvements, but what I wanted to know is if Bridge can generate somehow a similar output as openui5 expects to invest more time on it or not.

    Best regards,
    Manu

    #2
    i was looking at the generated code, it seems that it has not been finished. with retyped. they use software to generate c# Libraries from typescript files.

    https://github.com/DefinitelyTyped/D...query.sap.d.ts

    I am not apart of bridge, just a community member.

    openui5.JquerySap seems to be a type and not a static class like JQuery in retyped.

    Comment


      #3
      At the bottom:
      Linked to: https://github.com/SAP/openui5/issues/88


      Comment


        #4
        Hi samuelgrahame,

        I know the current retyped.openui5 is far from perfect and some guys are trying to get better typescript for openui5 in the thread you linked.

        However, ignoring that issue, if bridge.net is not able to generate a similar output as openui5 expects, I have a problem with bridge.net. That's why I asked the question in the first place. If bridge.net supports it somehow, I'll write a handmade c# library wrapper that suits better, but the current show stopper for me is in the bridge.net side.

        Best regards,
        Manu

        Comment


          #5
          Hi emumanu,

          Thanks for your interest in Bridge.NET and Retyped.

          Unfortunately, Bridge does not allow replacing standard type initialization with custom code.
          I'm not sure that it would be working, but you can try adding registration of a custom controller in a static constructor of your type.
          static CustomType()
          {
             sap.ui.define(new Func<sap.ui.core.mvc.Controller, object>(controller => {
                var baseTypeName = typeof(MyCmp).BaseType.FullName;
                return controller.extend(baseTypeName, /* ??? */ );
             }));
          }
          
          // Add missing "extend" method to the Controller type:
          [External]
          public static class Extensions
          {
              public static extern object extend(
                  this sap.ui.core.mvc.Controller controller, 
                  string typeName,
                  object members);
          }
          Question marks above should be replaced with an object containing members extending the base type - probably just providing an instance of the custom type would work: new CustomType().

          If you need to emit custom code for constructing an instance of your type, you can play with [Template] and [Constructor] attributes.

          Hope that helps.

          Comment


            #6
            Hi Andrey ,

            It's working!

            Thank you very much for your help. Best regards,
            Manu

            Comment


              #7
              Hi emumanu,

              When you get a chance, it would be very helpful to the community if you posted a code sample demonstrating how you solved the issue.

              We might also be able to offer some further suggestions regarding how to optimize your sample.

              Comment


                #8
                Hi geoffrey.mcgill ,

                the current openui5 wrapper is pretty inmature so Andrey's solution worked with a couple of hand made changes because of the wrapper.

                This is my source class:

                using System;
                using Retyped;
                using Bridge;
                using Bridge.Html5;
                using RTestLib;
                
                namespace ClientControllers
                {
                    public class Class4 : openui5.sap.ui.core.mvc.Controller
                    {
                        static Class4()
                        {
                            openui5.sap.ui.define(new string[] {
                                    "sap/ui/model/json/JSONModel",
                                    "sap/ui/core/mvc/Controller"
                                }, 
                                new Func<
                                    openui5.sap.ui.model.json.JSONModel, 
                                    openui5.sap.ui.core.mvc.Controller, object>(
                                    (JsonModel, controller) => {
                                        var baseTypeName = typeof(Class4).BaseType.FullName;
                                        return controller.extend(baseTypeName, new Class4(""));
                                    })
                            );
                        }
                
                        public Class4(Union<string, object[]> sName) : base(sName)
                        {
                        }
                
                        public void onInit()
                        {
                            Window.Alert($"Controller onInit called!");
                        }
                }
                This is the generated javascript by bridge:

                Bridge.assembly("RTestLib", function ($asm, globals) {
                    "use strict";
                
                    /**
                     * @public
                     * @class ClientControllers.Class4
                     * @augments Retyped..sap.ui.core.mvc.Controller
                     */
                    Bridge.define("ClientControllers.Class4", {
                        inherits: [sap.ui.core.mvc.Controller],
                        $metadata : function () { return {"att":1,"a":2,"m":[{"n":".cctor","t":1,"sn":"ctor","sm":true},{"a":2,"n":".ctor","t":1,"p":[System.Object],"pi":[{"n":"sName","pt":System.Object,"ps":0}],"sn":"ctor"},{"a":2,"n":"onInit","t":8,"sn":"onInit$1","rt":System.Void}]}; },
                        statics: {
                            ctors: {
                                ctor: function () {
                                    sap.ui.define(System.Array.init(["sap/ui/model/json/JSONModel", "sap/ui/core/mvc/Controller"], System.String), function (JsonModel, controller) {
                                        var baseTypeName = Bridge.Reflection.getTypeFullName(Bridge.Reflection.getBaseType(ClientControllers.Class4));
                                        return controller.extend(baseTypeName, new ClientControllers.Class4(""));
                                    });
                                }
                            }
                        },
                        ctors: {
                            ctor: function (sName) {
                                this.$initialize();
                                sap.ui.core.mvc.Controller.call(this, sName);
                            }
                        },
                        methods: {
                            onInit$1: function () {
                                window.alert(System.String.format("Controller onInit called!", null));
                            }
                        }
                    });
                });
                As the onInit method is not marked as virtual, the new one that hides the base one has signature onInit$1 on bridge. Also, I needed a parameterless constructor that was not present in the wrapper. With those two changes, it worked.

                My working solution is:

                Bridge.assembly("RTestLib", function ($asm, globals) {
                    "use strict";
                
                    /**
                     * @public
                     * @class ClientControllers.Class4
                     * @augments Retyped..sap.ui.core.mvc.Controller
                     */
                    Bridge.define("ClientControllers.Class4", {
                        inherits: [sap.ui.core.mvc.Controller],
                        $metadata : function () { return {"att":1,"a":2,"m":[{"n":".cctor","t":1,"sn":"ctor","sm":true},{"a":2,"n":".ctor","t":1,"p":[System.Object],"pi":[{"n":"sName","pt":System.Object,"ps":0}],"sn":"ctor"},{"a":2,"n":"onInit","t":8,"sn":"onInit$1","rt":System.Void}]}; },
                        statics: {
                            ctors: {
                                ctor: function () {
                                    sap.ui.define(System.Array.init(["sap/ui/model/json/JSONModel", "sap/ui/core/mvc/Controller"], System.String), function (JsonModel, controller) {
                                        var baseTypeName = Bridge.Reflection.getTypeFullName(Bridge.Reflection.getBaseType(ClientControllers.Class4));
                                        return controller.extend(baseTypeName, new ClientControllers.Class4());
                                    });
                                }
                            }
                        },
                        ctors: {
                            ctor: function () {
                                this.$initialize();
                                sap.ui.core.mvc.Controller.call(this);
                            }
                        },
                        methods: {
                            onInit: function () {
                                window.alert(System.String.format("Controller onInit called!", null));
                            }
                        }
                    });
                });
                I need to create a new wrapper to be able to use it without hand made changes, but this is promising.

                BR,
                Manu

                Comment


                  #9
                  Thanks for the update.

                  I believe the next release of Retyped will help with openui5. Lots of enhancements coming soon.

                  Comment


                    #10
                    Hello geoffrey.mcgill and Andrey,

                    unfortunately, after more tests, it is not working as expected, and it is partially working on Chrome but it is not working on Firefox.

                    You can reproduce it by downloading the code at https://sapui5.hana.ondemand.com/#/s...hrough.09/code (the link to download it is in the upper right corner), and run index.htm in a web server. You should see the text you type in the input field repeated on the right, with the "Hello " prefix, like this:
                    https://sapui5.hana.ondemand.com/tes...app/index.html

                    Now, replace the controller for a similar one using bridge.net. For that, first modify index.htm and load bridge.js before the sap.ui.getCore().attachInit() call. Then replace all the code in controller/App.controller.js with:

                    Bridge.assembly("TestLib", function ($asm, globals) {
                        "use strict";
                    
                        Bridge.define("sap.ui.demo.walkthrough.controller.App", {
                            inherits: [sap.ui.core.mvc.Controller],
                            ctors: {
                                ctor: function () {
                                    this.$initialize();
                                    sap.ui.core.mvc.Controller.call(this);
                                    window.alert("constructor");
                                }
                            },
                            methods: {
                                onShowHello: function () {
                                    // read msg from i18n model
                                    var oBundle = this.getView().getModel("i18n").getResourceBundle();
                                    var sRecipient = this.getView().getModel().getProperty("/recipient/name");
                                    var sMsg = oBundle.getText("helloMsg", System.Array.init([sRecipient], System.Object));
                                    // show message
                                    sap.m.MessageToast.show(sMsg);
                                }
                            }
                        });
                    });
                    
                    sap.ui.define(System.Array.init(["sap/ui/core/mvc/Controller", "sap/m/MessageToast"], System.String), function (Controller) {
                        // the next line is the problematic one
                        return sap.ui.core.mvc.Controller.extend(Bridge.Reflection.getTypeFullName(Bridge.Reflection.getBaseType(sap.ui.demo.walkthrough.controller.App)), new sap.ui.demo.walkthrough.controller.App());
                    });
                    that corresponds to this C# class:
                    using System;
                    using Bridge;
                    using Retyped;
                    
                    namespace SDKSamples
                    {
                        [FileName("App.controller")]
                        [Name("sap.ui.demo.walkthrough.controller.App")]
                        public class Walkthrough9 : sap.ui.core.mvc.Controller
                        {
                            [Init(InitPosition.Bottom)]
                            public static void Script()
                            {
                                sap.ui.define(new string[] {
                                        "sap/ui/core/mvc/Controller",
                                         "sap/m/MessageToast",
                                    },
                                    new Func<sap.ui.core.mvc.Controller, object>(
                                        (Controller) => {
                                            // the next line is the problematic one
                                            return sap.ui.core.mvc.Controller.extend(typeof(Walkthrough9).BaseType.FullName, new Walkthrough9());
                                        })
                                );
                            }
                    
                            public Walkthrough9() : base()
                            {
                                Window.Alert("constructor");
                            }
                    
                            public void onShowHello()
                            {
                                // read msg from i18n model
                                var oBundle = this.getView().getModel<sap.ui.model.resource.ResourceModel>("i18n").getResourceBundle();
                                var sRecipient = this.getView().getModel<sap.ui.model.json.JSONModel>().getProperty("/recipient/name");
                                var sMsg = oBundle.getText("helloMsg", new object[] { sRecipient });
                                // show message
                                sap.m.MessageToast.show(sMsg);
                            }
                        }
                    }
                    If you run it in Chrome, it calls the constructor twice and the sample works. If you run it in Firefox, it calls the constructor once but the sample does not work. It says:
                    failed to load 'sap/ui/demo/walkthrough/controller/App.controller.js' from /controller/App.controller.js: TypeError: "Object" is read-only

                    If you try the same changing the line that calls sap.ui.core.mvc.Controller.extend for:
                    return sap.ui.core.mvc.Controller.extend(typeof(Walkthrough9).FullName, new Walkthrough9());
                    the generated code changes to:
                    return sap.ui.core.mvc.Controller.extend(Bridge.Reflection.getTypeFullName(sap.ui.demo.walkthrough.controller.App), new sap.ui.demo.walkthrough.controller.App());
                    If you run it in Chrome, it calls the constructor once but the sample does not work. It says:
                    Uncaught (in promise) Error: failed to load 'sap/ui/demo/walkthrough/controller/App.controller.js' from /controller/App.controller.js: TypeError: Cannot assign to read only property 'App' of object '#<Object>'

                    If you run it in Firefox, it calls the constructor once and it says:
                    failed to load 'sap/ui/demo/walkthrough/controller/App.controller.js' from /controller/App.controller.js: TypeError: "App" is read-only

                    Note that what I want to replace is:
                    sap.ui.define([
                        "sap/ui/core/mvc/Controller",
                        "sap/m/MessageToast"
                    ], function (Controller) {
                        "use strict";
                    
                        return Controller.extend("sap.ui.demo.walkthrough.controller.App", {
                    
                            onShowHello : function () {
                                // read msg from i18n model
                                var oBundle = this.getView().getModel("i18n").getResourceBundle();
                                var sRecipient = this.getView().getModel().getProperty("/recipient/name");
                                var sMsg = oBundle.getText("helloMsg", [sRecipient]);
                    
                                // show message
                                sap.m.MessageToast.show(sMsg);
                            }
                        });
                    
                    });
                    Could you please help me?

                    Comment


                      #11
                      The problem with the "TypeError: Cannot assign to read only property..." is that Bridge creates the classes using defineProperty and openui5 uses the bridge object to populate a new object, writing that property. If I change the properties to be writable (in bridge.js this function at line 2962):

                          defineProperty: function (scope, name, cls) {
                                  Object.defineProperty(scope, name, {
                                      value: cls,
                                      enumerable: true,
                                      writable: true,
                                      configurable: true
                                  });
                              },
                      It works.

                      So, is there any way to define a Bridge class to be "writable" without that nasty hack? If not, could it be added to the next release please?

                      Comment


                        #12
                        Can you try setting the autoProperty Rule to "Plain" in your bridge.json.

                        https://github.com/bridgedotnet/Brid...n#autoproperty

                        HTML Code:
                        {
                            "rules": {
                                "autoProperty": "Plain"
                            }
                        }

                        Comment


                          #13
                          I've been thinking about another idea that could eliminate potential issues with Bridge types in the described scenario. It's about applying [ObjectLiteral] attribute to the Controller class inheritor, so the output will be closer to the target JavaScript code - just an object literal, the same as it is in JavaScript sample. There are a few restrictions connected with types marked with [ObjectLiteral] attribute, but in most cases they can be got round.

                          I didn't manage to adjust you C# sample, it looks it uses some custom entities named similarly to what openui5 provides. Below is a prototype of what I'm suggesting. Methods are declared as properties of delegate types - the reason is to get them emitted in the created object. Properties' initializers can't access non-static members, that is why _this field was added.

                          https://deck.net/500265c00e62bd7f9dd5bebbc66eb796

                          Probably this approach will fit better to your scenario.

                          Comment


                            #14
                            geoffrey.mcgill I don't have any properties in my class and I already had "autoProperty": "Plain" in bridge.json

                            The call to defineProperty is done when declaring the class using Bridge.define('sap.ui.demo.walkthrough.Component'. ..

                            Component is set in sap.ui.demo.walkthrough using defineProperty.

                            Comment


                              #15
                              Andrey, could you please elaborate more on the restrictions about your solution using [ObjectLiteral]?

                              I implemented something similar to create an object based on the class:

                              public static class Glue
                                  {
                                      public static object GetDefaultValue(Type type)
                                      {
                                          object output = null;
                              
                                          if (type.IsValueType) {
                                              output = Activator.CreateInstance(type);
                                          }
                              
                                          return output;
                                      }
                              
                                      public static object CreateRawClassObject(Type type)
                                      {
                                          dynamic returnObj = new object();
                              
                                          foreach (MethodInfo method in type.GetMethods().Where(m => !m.IsStatic)) {
                                              returnObj[method.ScriptName] = method.DeclaringType.Prototype.ToDynamic()[method.ScriptName];
                                          };
                              
                                          foreach (FieldInfo field in type.GetFields().Where(f => !f.IsStatic)) {
                                              returnObj[field.ScriptName] = GetDefaultValue(field.FieldType);
                                          };
                              
                                          return returnObj;
                                      }
                                  }
                              And then I use it like this:

                              return sap.ui.core.UIComponent.extend(nameof(Component), Glue.CreateRawClassObject(typeof(Component)));
                              Even if it works in a simple example, I wasn't sure about the limitations of this approach. Also, I wasn't able to return the init method bridge create to initialize the fields if they have default values.

                              Don't worry about adjusting to my openui5 code as I'm creating a new wrapper for it.
                              Last edited by emumanu; 2018-04-17 @ 08:40 PM.

                              Comment

                              Working...
                              X