Software7

Personal Developer Notebook

WebView JavaScript Native Interoperation in Windows 8.1

Changes in Window 8.1

First it should be mentioned, that there were a few changes in the JavaScript Native Interoperation for Windows 8.1. So the AllowedScriptNotifyUris are no longer supported. Instead you must include the page’s URL in the ApplicationContentUriRules section of the app manifests, for both of them, the Windows App and the Windows Phone App.

Interoperation

To be able to call native code from a web page in a WebView control,

  • The web site must have a valid SSL certificate
  • Like mentioned above the page’s URI must be added to the Content URIs section of the Package.appxmanifest
  • On the native side you add an ScriptNotify EventHandler to the WebView
  • Then you can fire the event from JavaScript by calling window.external.notify
  • String parameters can be used

The other way round you can call arbitrary JavaScript functions by using WebView’s InvokeScriptAsync method. There you can also add String parameters. It’s a good idea to make sure that the first call to JavaScript can only happen after the web page is loaded completely.

Screencast

The screencast shows again all steps and is divided into the following sections:

  • Embedding a WebView control
  • Calling native code via window.external.notify from JavaScript in the WebView
  • Adding the URL of the web page to the ApplicationContentUriRules section of the app manifest.
  • Calling a JavaScript function in the WebView from native code

Source Code

MainPage.xmal.cs

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;
using UniLayout.ViewModels;

// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=234238

namespace UniLayout
{
    /// <summary>
    /// An empty page that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainPage : Page
    {
        private MainPageViewModel _mainPageViewModel;
        private bool _isInUpdate = true;

        public MainPage()
        {
            this.InitializeComponent();

            this.SizeChanged += MainPage_SizeChanged;
            WebView.ScriptNotify += WebView_ScriptNotify;
            WebView.Loaded += WebView_Loaded;

            _mainPageViewModel = DataContext as MainPageViewModel;
            _mainPageViewModel.Update();
        }

        void WebView_Loaded(object sender, RoutedEventArgs e)
        {
            _isInUpdate = false;
        }

        void WebView_ScriptNotify(object sender, NotifyEventArgs e)
        {
            _isInUpdate = true;
            int value = int.Parse(e.Value);
            _mainPageViewModel.SliderValue = value;
            _isInUpdate = false;
        }

        void MainPage_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            MainPageViewModel mainPageViewModel = DataContext as MainPageViewModel;
            mainPageViewModel.Update();
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            WebView.Navigate(new Uri("https://www.software7.biz/test/windowsJSInteraction/winjsinteract.html"));
        }


        private async void SetWebViewSlider(int sliderValue)
        {
            if(!_isInUpdate)
                await WebView.InvokeScriptAsync("SetSliderValue", new String[] {sliderValue.ToString()});
        }

        private void Slider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e)
        {
            SetWebViewSlider((int)(e.NewValue + .5));
        }
    }
}

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:UniLayout"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ViewModels="using:UniLayout.ViewModels"
    xmlns:converter="using:UniLayout.Converter"
    x:Class="UniLayout.MainPage"
    mc:Ignorable="d">

    <Page.DataContext>
        <ViewModels:MainPageViewModel/>
    </Page.DataContext>
    
    <Page.Resources>
         <converter:IntToGridLengthConverter x:Key="IntToGridLengthConverter"/>
         <converter:WidthToRotatedMarginConverter x:Key="WidthToRotatedMarginConverter"/>
        <converter:IntToStringConverter x:Key="IntToStringConverter"/>
    </Page.Resources>

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <Grid.RowDefinitions>
            <RowDefinition Height="{Binding Path=MainTitleHeight, Converter={StaticResource IntToGridLengthConverter}}"/>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="{Binding Path=SubTitleHeight, Converter={StaticResource IntToGridLengthConverter}}"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>

        <Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2" Background="DarkSlateGray">
            <TextBlock Text="{Binding Path=MainTitle}" 
                       HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       FontSize="{Binding FontSizeMainTitle}" 
                       TextAlignment="Center"/>
        </Border>

        <Border Grid.Row="1" Grid.Column="0"  Background="DarkKhaki">
            <TextBlock 
                Text="{Binding Path=UpperTitle}" 
                FontSize="{Binding FontSizeSubTitle}" Width="{Binding UpperTitleWidth}" Height="{Binding UpperTitleHeight}" 
                Margin="{Binding Path=UpperTitleWidth, Converter={StaticResource WidthToRotatedMarginConverter}}" 
                TextAlignment="Center" 
                RenderTransformOrigin="0.5, 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform Angle="-90"/>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Border>

        <Border Grid.Row="2" Grid.Column="0"  Background="DarkOrange">
            <TextBlock 
                Text="{Binding Path=LowerTitle}" 
                FontSize="{Binding FontSizeSubTitle}" Width="{Binding Path=LowerTitleWidth}" Height="{Binding Path=LowerTitleHeight}" 
                Margin="{Binding Path=LowerTitleWidth, Converter={StaticResource WidthToRotatedMarginConverter}}" 
                TextAlignment="Center" 
                RenderTransformOrigin="0.5, 0.5">
                <TextBlock.RenderTransform>
                    <RotateTransform Angle="-90"/>
                </TextBlock.RenderTransform>
            </TextBlock>
        </Border>

        <Border Grid.Row="1" Grid.Column="1"  Background="CadetBlue">
            <StackPanel Margin="20">
                <StackPanel Orientation="Horizontal">
                    <TextBlock FontSize="20" Text="Native Slider:" Margin="0,0,12,0"/>
                    <TextBlock FontSize="20" Text="{Binding Path=SliderValue, Converter={StaticResource IntToStringConverter}}"/>
                </StackPanel>
                <Slider Value="{Binding Path=SliderValue, Mode=TwoWay}" Minimum="0" Maximum="100" ValueChanged="Slider_ValueChanged"/>
            </StackPanel>
        </Border>

        <Border Grid.Row="2" Grid.Column="1"  Background="AntiqueWhite">
            <WebView x:Name="WebView"/>
        </Border>


    </Grid>
</Page>


winjsinteract.html

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=400"/>
    <title></title>

    <link rel="stylesheet" href="js/jquery.mobile-1.4.5.min.css" />
    <script type="text/javascript" src="js/jquery-1.11.2.min.js"></script>
    <script type="text/javascript" src="js/jquery.mobile-1.4.5.min.js"></script>

</head>


<body>

<div id="content-div" style="margin:20px">
    <div id="slider-div">
        <input type="range" id="slider" value="50" min="0" max="100">
    </div>
</div>

<script type="application/javascript">

    $("#slider-div").change(function() {
        var value = $("#slider").val();
        window.external.notify(value);
    })

    function SetSliderValue(newValueFromNative) {
        $("#slider").val(newValueFromNative);
        $("#slider").slider("refresh");
    }

</script>

</body>
</html>