Выполнение JavaScript в WebView Xamarin Forms
В существующем элементе WebView имеется функция для выполнения JavaScript кода после загрузки страницы, однако она не позволяет получить результат выполнения. В этой статье мы реализуем эту возможность через связываемое свойство (BindableProperty).
Расширяем WebView
Создадим наследника класса WebView и добавим к нему функцию EvaluateJavascript
, которая будет принимать строку для выполнения и возвращать строку с результатом.
public class WebViewer : WebView
{
public static BindableProperty EvaluateJavascriptProperty =
BindableProperty.Create(nameof(EvaluateJavascript), typeof(Func<string, Task<string>>), typeof(WebViewer), null, BindingMode.OneWayToSource);
public Func<string, Task<string>> EvaluateJavascript
{
get { return (Func<string, Task<string>>)GetValue(EvaluateJavascriptProperty); }
set { SetValue(EvaluateJavascriptProperty, value); }
}
}
Рендереры
Теперь нам нужно добавить кастомный рендерер для каждой платформы, чтобы реализовать новую функцию.
Android
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace Mobile.Droid
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as WebViewer;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
var reset = new ManualResetEvent(false);
var response = string.Empty;
Device.BeginInvokeOnMainThread(() =>
{
Control?.EvaluateJavascript(js, new JavascriptCallback((r) => { response = r; reset.Set(); }));
});
await Task.Run(() => { reset.WaitOne(); });
return response;
};
}
}
internal class JavascriptCallback : Java.Lang.Object, IValueCallback
{
public JavascriptCallback(Action<string> callback)
{
_callback = callback;
}
private Action<string> _callback;
public void OnReceiveValue(Java.Lang.Object value)
{
_callback?.Invoke(Convert.ToString(value));
}
}
}
iOS
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace Mobile.iOS
{
public class WebViewRender : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var webView = e.NewElement as WebViewer;
if (webView != null)
webView.EvaluateJavascript = (js) =>
{
return Task.FromResult(this.EvaluateJavascript(js));
};
}
}
}
UWP
[assembly: ExportRenderer(typeof(WebViewer), typeof(WebViewRender))]
namespace Mobile.UWP.CustomRenderers
{
public class WebViewRender : WebViewRenderer
{
protected async override void OnElementChanged(ElementChangedEventArgs<WebView> e)
{
base.OnElementChanged(e);
var webView = e.NewElement as WebViewer;
if (webView != null)
webView.EvaluateJavascript = async (js) =>
{
return await Control.InvokeScriptAsync("eval", new[] { js });
};
}
}
}
Вызов из модели представления
Первым делом, нам нужно создать свойство в нашей модели представления (ViewModel), как показано ниже.
private Func<string, Task<string>> _evaluateJavascript;
public Func<string, Task<string>> EvaluateJavascript
{
get { return _evaluateJavascript; }
set { _evaluateJavascript = value; }
}
Далее, нужно связать свойство из модели представления с элементом управления (контролом).
<control:WebViewer Source="{Binding WebViewSource}"
EvaluateJavascript="{Binding EvaluateJavascript}, Mode=OneWayToSource}" />
Теперь мы можем вызвать нашу функцию из модели представления и получить результат.
var result = await EvaluateJavascript("document.getElementById('test');");
Предупреждения
Есть большая ловушка, с которой вы можете столкнуться, выполнение JavaScript, которое придется принять к сведению.
Android
В Android версии 4.1 и ниже вы можете легко использовать этот JavaScrip и возвращать результат.
document.getElementById('myElement').value;
Но, в версии 4.2 изменен JavsScript движок, поэтому когда вы запускаете указанный выше код, он сначала возвращает правильный результат, а потом устанавливает весь объект из DOM в результат выполнения этого сценария. Теперь, если вы снова выполните код выше, ваш скрипт не сможет найти элемент, потому что он больше не существует, т.к. весь ваш DOM - это прошлый результат.
Чтобы обойти эту проблему, вы должны присвоить результат вашего скрипта переменной. Вам не нужно возвращать значение самой переменной, достаточно просто установить ей значение, и оно будет возвращено, не затрагивая DOM существующей страницы.
var x = document.getElementById('myElement').value;
Теги: xamarin forms, javascript, c#, перевод