Page 1 of 2

custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Oct 12, 2016 12:12 pm
by Sesztak
Dear JS-Support,

We have a fundamental question:

We would like to implement some custom control (inherited from Control or UserControl).

This custom controls or userControl controls use 3rd party javascript libraries, as e.g. jQuery or jQuery UI.

To handle native html CSHTML5 advise to use following pattern:
1.) -in control constructor set the HtmlRepresentation by CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(),
+add Loaded even.
-in Control_Loaded event: check it is in visual tree and add actions, like: (Action<object>)this.OnChanged...

2.) So, normally the custom control instantiated (constructor hitted !) before your other advise occured:
Page_Loaded => LoadCssFile () & LoadJavaScriptFile().

According 1.) & 2.) : there is an antagonism / contradiction :
If you do that in that order (first point 1. and 2. point after): wo will get exception: external javascript and/or css not yet loaded.
How to use it ?

The opposite way (point 2. first, and point 1 after) is not possible or not known by us.

So, our question: how to do both : implement custom control based on 3rd party *.js / *.css ?
What is the preferred order, policy ?

Thanks in advance,
Br,
Péter

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Fri Oct 14, 2016 3:26 am
by JS-Support @Userware
Hi Péter,

- Even though "SetHtmlRepresentation" is done in the constructor, the string you pass to it is used only later when the element is added to the Visual Tree.

- Additionally, the string you pass to "SetHtmlRepresentation" usually does not contain anything that requires particular scripts: in fact, even though they may have IDs that may be used later by some scripts, the stuff you add usually are things such as DIVs, <input> tags, and other standard HTML dom elements. If you need to add anything else special, that requires a script to be loaded before, please let me know.

Thanks.
Regards,
JS-Support

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Fri Oct 14, 2016 3:50 am
by Sesztak
Dear JS-Support,
thanks for your kind reply:
"script to be loaded before, please let me know.": this is what we need exactly ! :)
Would you be so kind to help us, how to do that ?

Thanks in advance,
Br,
Péter

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Fri Oct 14, 2016 3:56 am
by JS-Support @Userware
Hi Péter,

By "please let me know", I mean please let me know what you need to pass to "SetHtmlRepresentation" that requires a script to be loaded before.

Thanks.
Regards,
JS-Support

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Thu Sep 07, 2017 6:18 am
by kmatt
I have an example I need this type of functionality for. I am trying to subclass the Kendo UI controls, I'm starting with button using MVVM. The actual button gets instantiated in xaml like so:
xmlns:kendoUI="clr-namespace:GUI.Controls.Kendo"
<kendoUI:KendoUIButton x:Name="OK" Caption="OK"/>

The button code derives currently from FrameworkElement:
namespace GUI.Controls.Kendo
{
public partial class KendoUIButton : FrameworkElement
{
public KendoUIButton()
{
InitializeComponent();
CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(this, $@"<button id=""{Name}"" Name=""{Name}"" data-bind=""events:{{click: listener, dblclick: listener, mouseover: listener, mouseout: listener}}"" class=""k-button k-primary""/>");
Loaded += KendoUIButton_Loaded;
}
Name however does not have a value yet in the constructor.
private void KendoUIButton_Loaded(object sender, RoutedEventArgs e)
{
var control = (KendoUIButton)sender;
CSharpXamlForHtml5.DomManagement.SetHtmlRepresentation(control, $@"<button id=""{Name}"" Name=""{Name}"" data-bind=""events:{{click: listener, dblclick: listener, mouseover: listener, mouseout: listener}}"" class=""k-button k-primary""/>");
Interop.ExecuteJavaScript($@"
$(document).ready(function() {{
var viewModel = kendo.observable({{
text: ""{Caption}"",
listener: function(e) {{$0(e);}}
}});
kendo.bind($(""#{Name}""), viewModel);
}});",
(Action<object>)OnButtonCallback);
}
public void OnButtonCallback(object e){}
}
}
If we leave it like this, we get a blank button that does not respond to the click, but no errors. We tried replacing $(""#{Name}"") with document.getElementsByName, it returns a zero length array. If we do not have the second SetHtmlRepresentation call, the button has no id or name since the Name property is not valid yet in the constructor. How can we accomplish this?

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Thu Sep 07, 2017 6:54 am
by JS-Support @Userware
Hi,

If you need to set the Html at a later time (later than the constructor), you can follow this alternative approach:

1. Inherit from "HtmlPresenter" instead of "Control" or "UserControl"
2. Call:

Code: Select all

this.Html = @"<Enter your Html here>";


Alternatively, if you want to inherit from UserControl, you can do so and declare a <HtmlPresenter/> in the XAML of the UserControl.

The documentation for the HtmlPresenter control can be found at: http://cshtml5.com/links/how-to-use-the-htmlpresenter.aspx

Thanks.
Regards,
JS-Support

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Sep 13, 2017 7:23 am
by kmatt
FYI, when I inherit from HtmlPresenter:

namespace GUI.KendoExtension
{
public partial class KendoUIButton : HtmlPresenter
{
...
}
}

I get:

Exception thrown: 'System.InvalidCastException' in CSharpXamlForHtml5.dll

Additional information: Unable to cast object of type 'GUI.KendoExtension.KendoUIButton' to type 'Windows.UI.Xaml.Controls.ContentControl'.

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Sep 13, 2017 7:44 am
by TaterJuice
kmatt wrote:FYI, when I inherit from HtmlPresenter:
Exception thrown: 'System.InvalidCastException' in CSharpXamlForHtml5.dll

Additional information: Unable to cast object of type 'GUI.KendoExtension.KendoUIButton' to type 'Windows.UI.Xaml.Controls.ContentControl'.


What does your XAML look like? That error says you're trying to cast your KendoUIButton class to a ContentControl, and that must be happening somewhere else in your code - your KendoUIButton is not a ContentControl, its a UserControl which derives from an HTMLPresenter. You can't cast it to a ContentControl, but you can use it as a DataTemplate for a ContentControl or ContentPresenter, or for items in an ItemsPresenter (like ListBox).

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Sep 13, 2017 8:10 am
by kmatt
It happened here:

public sealed partial class App : Application
{
/// <summary>
/// Initializes a new instance of the <see cref="App" /> class.
/// </summary>
public App()
{
InitializeComponent();
try
{
var mainPage = new MainPage();
Window.Current.Content = mainPage; <---


Where
<Page x:Class="GUIProject.MainPage"
and inside of it was the KendoUIButton control:
<kendoUI:KendoUIButton x:Name="LoginButton" Caption="Login"/>
I got the same kind of casting error when I tried to inherit from UserControl as well. ButtonBase however works, since it derives from ContentControl.

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Sep 13, 2017 11:37 am
by TaterJuice
So, if I understand you correctly, you're trying to create a clickable button which can have dynamic HTML content, including custom\external JS libraries and custom\external CSS?

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Sep 13, 2017 12:25 pm
by kmatt
I'm just trying to wrap the Kendo UI javascript/html5 Button element. I tried the Button first thinking it would be simplest and I could use what I learned and extend it to the other Kendo UI components. I want to wrap a number of them including the grid, I was thinking I would release it as an extension after getting it to work. As you can see, I did not cast that control to a ContentControl or anything derived from it, but I believe that app.xaml.cs is pretty much your demo code. I've tried every combination of UserControl, HtmlPresenter, ContentControl and ButtonBase I can find, they all have some problem. Using ButtonBase or ContentControl with an HtmlPresenter inside I can see the Kendo UI button, but I also see the ContentControl behind it on the GUI and I can't get rid of it. Using UserControl with HtmlPresenter inside or just plain HtmlPresenter as the control type I inherit from gives the error I showed above.

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Wed Sep 13, 2017 1:46 pm
by TaterJuice
Can you share a sample project like what you just described? I think I might misunderstand your issue, but I'll give it a try anyway. A Sample project would be helpful in helping you troubleshoot.

-------

So, I've never used KendoUI, and I'm not sure I understand your issue completely, but I do know how to work with the CSHTML5 Interop to build a plugin for a JS library...

For my (SUPER CONTRIVED) test, I've created a custom control with the following specifications, class KendoUIButton.
1. A Button who's content is made of HTML, CSS and Javascript
2. Control the HTML and CSS content of the button with calls to Javascript functions (for your needs, you'll replace my JS functions with KendoUI functions)

Our goal is to dynamically update the Button's HTML content and CSS properties via JS Interop (we will give it a random string of text between 5 and 10 characters in length, and a randomized css color name from a predefined list of 5 colors)

Keep in mind - Changing a Control's Content and Foreground color can be done MUCH simpler using the built-in dependency properties, bindings, visual states, styles, events, and much more. This is just an example of interacting with JS Interop in order to build a plugin for a JS library.

This control is defined as follows:

C#

Code: Select all

using CSHTML5;
using System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace HTMLButtonTest
{
    public partial class KendoUIButton : UserControl
    {
        private Random rand = new Random();
        public KendoUIButton()
        {
            this.InitializeComponent();
            this.ButtonControl.Visibility = Visibility.Collapsed; // hide the button until we populate its content
            this.Loaded += KendoUIButton_Loaded;
        }

        private void KendoUIButton_Loaded(object sender, RoutedEventArgs e)
        {
            ChangeButtonWithJSInterop("Click Me!", "black");
        }
        private void ChangeButtonWithJSInterop(string newHtmlContent, string cssColorname)
        {
            if(ButtonControl.Visibility != Visibility.Visible)
                ButtonControl.Visibility = Visibility.Visible;
            //Set the html content via javascript
            Interop.ExecuteJavaScript(@"$1.innerHTML = $0;", newHtmlContent, Interop.GetDiv(HTMLControl));
            //Set the css color via javascript
            Interop.ExecuteJavaScript(@"$1.style.color = $0;", cssColorname, Interop.GetDiv(HTMLControl));
        }

        private void ButtonControl_Click(object sender, RoutedEventArgs e)
        {
            //On Click, set the button's HTML content to a random string of text between 5 and 10 characters long, and a random CSS color name
            ChangeButtonWithJSInterop(new StringGenerator().MakeString(rand.Next(5,10)), GetRandomCSSColorName());
        }
        private string GetRandomCSSColorName()
        {
            switch(rand.Next(1,5))
            {
                case 1: return "blue";
                case 2: return "red";
                case 3: return "green";
                case 4: return "purple";
                default: return "black";
            }
        }
    }
}


XAML

Code: Select all

<UserControl
    x:Class="HTMLButtonTest.KendoUIButton"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:HTMLButtonTest">
    <Button
        x:Name="ButtonControl"
        Click="ButtonControl_Click"
        Margin="10"
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
        <native:HtmlPresenter
            x:Name="HTMLControl"
            xmlns:native="using:CSHTML5.Native.Html.Controls"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="native" />
    </Button>
</UserControl>


Full sample project, including supporting Code for my "StringGenerator" class, can be found in the .zip archive of my sample project, which I've uploaded here: https://www.dropbox.com/s/fcsaj5uwh6mhcfh/HTMLButtonTest.zip?dl=0

Conclusion:
Assuming I understood you correctly - this is a working example of interacting with CSS and Javascript to build a JS extension. The next steps would be to use Interop.LoadJavascriptFile("hostedurl") to load your external KendoUI library, and then start replacing your Javascript functions - ie, replace ""$1.innerHTML = $0;" with "$1.someKendoUIJSFunction($0);"

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Thu Sep 14, 2017 4:45 am
by kmatt
Awesome, thank you so much!!!

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Thu Sep 14, 2017 6:05 am
by kmatt
I tried this with my control. The problem is that you aren't actually wrapping a javascript visual control, you are setting properties on the HtmlPresenter control or the Xaml Button control, I'm not totally sure. If your InnerHtml had included <button/> code, you would see what I see - a grey button (from the xaml Button) in the back (underneath) the third party button control (the one presented with the innerhtml). I could already get this result as I stated before.
I tried your code WITHOUT the xaml Button control, using only the HtmlPresenter inside the UserControl, I still see the same type of thing - a clickable grey UserControl with the third party button on top of it. Setting the background and foreground of the UserControl to Transparent doesn't help, either. The only workable solution I can see would be to use the HtmlPresenter as the inherited class, but that gives the error in your code in App.xaml.cs.

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Thu Sep 14, 2017 7:13 am
by TaterJuice
Ah, I understand. Can you share a sample project where you have the KendoUI Button displaying, and give me an example of something you want to DO with the button (ie, what do you to do when the button is clicked?). I'm pretty sure I can help get you going.

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Thu Sep 14, 2017 12:43 pm
by kmatt
Here you go.

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Mon Sep 18, 2017 8:42 am
by TaterJuice
kmatt wrote:Here you go.

Working on this, but encountered a bug that I've reported here:
http://forums.cshtml5.com/viewtopic.php?f=5&p=9563#p9563

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Mon Sep 25, 2017 9:47 pm
by TaterJuice
So your issue with the extra <Button /> seems to be something kendoUI is doing, but has an easy fix. I added the following style to the page and the extra button disappears:

Code: Select all

//HideExtraButton.css
.k-button + button {
    display:none;
}


Image

Code: Select all

 await Interop.LoadCssFile("ms-appx:///HTMLButtonTest/Styles/HideExtraButton.css");


That said, I can't get any of your kendoUI Listeners to attach using JS Interop. The functions all work in the simulator, but they produce an exception in the JS output:

Uncaught Error: The function 'System.Void HTMLButtonTest.KendoUIButton::<OnDependanciesLoaded>b__3_0()' could not be translated.


I've tried a variety of solutions, but as soon as I execute kendo JS or pass an Action to the button listeners, I receive the above error.

Code: Select all

        private void OnDependanciesLoaded()
        {
            this.Dispatcher.BeginInvoke(() =>
            {
                var buttontext = String.Format(@"<button type='button' id='{0}' name='{0}' data-bind='events:{{click: listener, dblclick: listener}}' class='k-button k-primary'>{1}<button/>", Name, Caption);
                HTMLControl.Html = buttontext;
                Interop.ExecuteJavaScript(@"
                    window.jQuery('#" + Name + @"').click(function() {
                        alert('Clicked!');
                    });", (Action)OnButtonCallback);

                //Uncaught Error: The function 'System.Void HTMLButtonTest.KendoUIButton::<OnDependanciesLoaded>b__3_0()' could not be translated.
                //Interop.ExecuteJavaScript(String.Format(@"
                //    window.jQuery(document).ready(function() {{
                //        var viewModel = kendo.observable({{
                //            text: '{0}',
                //            listener: function() {{
                //                alert('Clicked!');
                //            }}
                //        }});
                //        kendo.bind(document.getElementById('{1}'), viewModel);
                //    }});", Caption, Name));

                //Uncaught Error: The function 'System.Void HTMLButtonTest.KendoUIButton::<OnDependanciesLoaded>b__3_0()' could not be translated.
                //Interop.ExecuteJavaScript(String.Format(@"
                //    window.jQuery(document).ready(function() {{
                //        var viewModel = kendo.observable({{
                //            text: '{0}',
                //            listener: function(e) {{
                //              $0(e)
                //            }}
                //        }});
                //        kendo.bind(document.getElementById('{1}'), viewModel);
                //    }});", Caption, Name), (Action<object>)OnButtonCallback);

                //Uncaught Error: The function 'System.Void HTMLButtonTest.KendoUIButton::<OnDependanciesLoaded>b__3_0()' could not be translated.
                //Interop.ExecuteJavaScript(String.Format(@"
                //    window.jQuery(document).ready(function() {
                //        var viewModel = kendo.observable({
                //            text: '{0}',
                //            listener: $0
                //        });
                //        kendo.bind(document.getElementById('{1}'), viewModel);
                //    });", Caption, Name), (Action<object>)OnButtonCallback);

                //Uncaught Error: The function 'System.Void HTMLButtonTest.KendoUIButton::<OnDependanciesLoaded>b__3_0()' could not be translated.
                //Interop.ExecuteJavaScript(@"
                //    window.jQuery('#" + Name + @"').click(function() {
                //        $0()
                //    });", (Action<object>)OnButtonCallback);

            });
        }


I've attached the updated project. Yay, we hid the button! :-/

https://www.dropbox.com/s/zoamzda88zcabq1/htmlbuttontest3.zip?dl=0

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Tue Sep 26, 2017 12:46 am
by JS-Support @Userware
TaterJuice wrote:I receive the above error


Please make sure that the first argument of the method ExecuteJavaScript is a string "literal", which means that its value is known ad design-time rather than runtime. For example, you cannot concatenate two strings with "string1" + "string2", and you also cannot use the "String.Format" method. Instead, if you want to pass content that changes at runtime, you need to pass it via the other arguments of the method, and use $0, $1, etc.

To better understand this issue and how to fix it, please read the paragraph that says "IMPORTANT" on the following page:
http://cshtml5.com/links/how-to-call-javascript.aspx

Thanks.
Regards,
JS-Support

Re: custom control or UserControl vs 3rd party javascript library as e.g. jQuery

Posted: Tue Sep 26, 2017 7:12 am
by TaterJuice
JS-Support wrote:
TaterJuice wrote:I receive the above error


Please make sure that the first argument of the method ExecuteJavaScript is a string "literal", which means that its value is known ad design-time rather than runtime. For example, you cannot concatenate two strings with "string1" + "string2", and you also cannot use the "String.Format" method. Instead, if you want to pass content that changes at runtime, you need to pass it via the other arguments of the method, and use $0, $1, etc.

To better understand this issue and how to fix it, please read the paragraph that says "IMPORTANT" on the following page:
http://cshtml5.com/links/how-to-call-javascript.aspx

Thanks.
Regards,
JS-Support


Thanks for the info, I've adjusted the calls to Interop but I still can't get it to work in the JS output. I was using String.Format() to try to work around the following issues. Here's what I've tried:

Code: Select all

Interop.ExecuteJavaScript(@"
   window.jQuery(document).ready(function() {
      var viewModel = kendo.observable({
         text: $0,
         listener: function() {
            alert('Clicked!');
         }
      });
      kendo.bind(document.getElementById($1), viewModel);
   });", Caption, Name);

Simulator: Works
JS Output:
Uncaught TypeError: this.get_Caption is not a function
at HTMLDocument.<anonymous> (HTMLButtonTest.js?20179260809:154)
at j (jquery-3.1.1.min.js:2)
at k (jquery-3.1.1.min.js:2)



Code: Select all

Interop.ExecuteJavaScript(@"
   window.jQuery(document).ready(function() {
      var viewModel = kendo.observable({
         text: '$0',
         listener: function() {
            alert('Clicked!');
         }
      });
      kendo.bind(document.getElementById($1), viewModel);
   });", Caption, Name);

Simulator: Works
JS Output:
Uncaught SyntaxError: Unexpected identifier

Uncaught TypeError: Cannot read property 'App' of undefined
at runMain (index.html:32)
at browserFinishedLoadingCallback (JSIL.Browser.js?20179260812:1138)