WKWebView.EvaluateJavaScript never returns - javascript

I'm trying to get a value from my javascript code back to my application using WKWebView.EvaluateJavaScript(string, WKJavascriptEvaluationResult). The problem is, the delegate WKJavascriptEvaluationResult is never called. Here's my code:
C#:
TaskCompletionSource<NSObject> tcs = new TaskCompletionSource<NSObject>();
webView.EvaluateJavaScript(javascript, (result, error) =>
{
tcs.SetResult(result);
});
return tcs.Task.Result;
Javascript:
function a()
{
return "test";
}
a();
The application is stuck on the Wait() call and never returns because the WKJavascriptEvaluationResult never gets called. Is there a know issue that I'm not aware of? Is there a better way to get values from my javascript code to my application, or am I not using it correctly?
Note: I'm using a TaskCompletionSource simply to make the whole method synchronous.

Worked for me in Xamarin Forms should be the same if you are using Xamarin IOS.
public class IosWebViewRenderer : ViewRenderer<HybridWebView, WKWebView>
{
const string HtmlCode = "document.body.outerHTML";
protected override void OnElementChanged(ElementChangedEventArgs<HybridWebView> e)
{
base.OnElementChanged(e);
if (Control == null)
{
WKWebView wKWebView = new WKWebView(Frame, new WKWebViewConfiguration());
wKWebView.NavigationDelegate = new WebViewDelate();
SetNativeControl(wKWebView);
}
if (e.NewElement != null)
{
NSUrlRequest nSUrlRequest = new NSUrlRequest(new NSUrl(hybridWebView.Uri.ToString()));
Control.LoadRequest(nSUrlRequest);
}
}
class WebViewDelate : WKNavigationDelegate
{
public override void DidFinishNavigation(WKWebView webView, WKNavigation navigation)
{
WKJavascriptEvaluationResult handler = (NSObject result, NSError error) => {
if (error != null)
{
Console.WriteLine(result.ToString());
}
};
webView.EvaluateJavaScript(HtmlCode, handler);
}
}
}

Why don't you use method EvaluateJavaScriptAsync instead of EvaluateJavaScript? I think it leads to much better readability of code - and it might even uses a TaskCompletionSource under the hood :)
try
{
var obj = await Control.EvaluateJavaScriptAsync(js).ConfigureAwait(true);
if (obj != null)
response = obj.ToString();
}
catch (Exception) { /* The Webview might not be ready... */ }

Related

Blazor await JSRuntime.InvokeAsync<string> capturing image src in C# returns null when I can observe in JS value being captured

I am using Blazor to capture a frame from the camera as base64 encoded image
I have a weird issue when I place a breakpoint in JS code I can see image src being captured
however, it never comes out of C# await JSRuntime.InvokeAsync<string> end.
At this point, I feel like I am banging my head on the wall.
Since the string is quite long I introduced a Timeout timespan of one minute, but that didn't help
My code:
Index.Razor
<div>
<img id="captured-image" /> </div>
<div>
<button #onclick="Save">Save</button> </div>
#code{
int index = 0;
protected override void OnInitialized()
{
index = localStorage.Length();
}
public async Task Save()
{
var src = await JSRuntime.InvokeAsync<string>("WebCamFunctions.getCapturedElement", new TimeSpan(0,1,0), new { capturedImageId = "captured-image" });
localStorage.SetItem<string>((index + 1).ToString(), src);
} }
m.js
window.WebCamFunctions = {
getCapturedElement: (options) => { getCapturedElement(options); }
};
function getCapturedElement(options) {
var base64EncodedImage = document.getElementById(options.capturedImageId).currentSrc;
return base64EncodedImage;
}
Why is Blazor await JSRuntime.InvokeAsync<string> capturing image src in C# returns null when it gets the value in JS?
EDIT:
I tried capturing an empty image it is in JS src = '' it still is null var src = await JSRuntime.InvokeAsync<string> so probably it is not length related.
window.WebCamFunctions = {
getCapturedElement: (options) => { getCapturedElement(options); }
};
That lambda doesn't return anything (or void ~= null). You need to return the return value
getCapturedElement: (options) => { return getCapturedElement(options); }
or shorter
getCapturedElement: (options) => getCapturedElement(options)
Easy to make a mistake like this. You could inline the function
window.WebCamFunctions = {
getCapturedElement: function (options) {
var base64EncodedImage = document.getElementById(options.capturedImageId).currentSrc;
return base64EncodedImage;
}
}

How to prevent Dynamics CRM 2015 from creating an opportunity when a lead is qualified?

Requirements when clicking the Qualify button in the Lead entity form:
Do not create an Opportunity
Retain original CRM qualify-lead JavaScript
Detect duplicates and show duplicate detection form for leads
Redirect to contact, either merged or created version, when done
The easiest approach is to create a plugin running on Pre-Validation for message "QualifyLead". In this plugin you simply have to set CreateOpportunity input property to false. So it would look like:
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
context.InputParameters["CreateOpportunity"] = false;
}
Or you can go with more fancy way:
public void Execute(IServiceProvider serviceProvider)
{
IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
var qualifyRequest = new QualifyLeadRequest();
qualifyRequest.Parameters = context.InputParameters;
qualifyRequest.CreateOpportunity = false;
}
Remember that it should be Pre-Validation to work correctly. Doing it like that allows you to remain with existing "Qualify" button, without any JavaScript modifications.
So Pawel Gradecki already posted how to prevent CRM from creating an Opportunity when a Lead is qualified. The tricky part is to make the UI/client refresh or redirect to the contact, as CRM does nothing if no Opportunity is created.
Before we begin, Pawel pointed out that
some code is not supported, so be careful during upgrades
I don't have experience with any other versions than CRM 2015, but he writes that there are better ways to do this in CRM 2016, so upgrade if you can. This is a fix that's easy to implement now and easy to remove after you've upgraded.
Add a JavaScript-resource and register it in the Lead form's OnSave event. The code below is in TypeScript. TypeScript-output (js-version) is at the end of this answer.
function OnSave(executionContext: ExecutionContext | undefined) {
let eventArgs = executionContext && executionContext.getEventArgs()
if (!eventArgs || eventArgs.isDefaultPrevented() || eventArgs.getSaveMode() !== Xrm.SaveMode.qualify)
return
// Override the callback that's executed when the duplicate detection form is closed after selecting which contact to merge with.
// This callback is not executed if the form is cancelled.
let originalCallback = Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication
Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication = (returnValue) => {
originalCallback(returnValue)
RedirectToContact()
}
// Because Opportunities isn't created, and CRM only redirects if an opportunity is created upon lead qualification,
// we have to write custom code to redirect to the contact instead
RedirectToContact()
}
// CRM doesn't tell us when the contact is created, since its qualifyLead callback does nothing unless it finds an opportunity to redirect to.
// This function tries to redirect whenever the contact is created
function RedirectToContact(retryCount = 0) {
if (retryCount === 10)
return Xrm.Utility.alertDialog("Could not redirect you to the contact. Perhaps something went wrong while CRM tried to create it. Please try again or contact the nerds in the IT department.")
setTimeout(() => {
if ($("iframe[src*=dup_warning]", parent.document).length)
return // Return if the duplicate detection form is visible. This function is called again when it's closed
let leadId = Xrm.Page.data.entity.getId()
$.getJSON(Xrm.Page.context.getClientUrl() + `/XRMServices/2011/OrganizationData.svc/LeadSet(guid'${leadId}')?$select=ParentContactId`)
.then(r => {
if (!r.d.ParentContactId.Id)
return RedirectToContact(retryCount + 1)
Xrm.Utility.openEntityForm("contact", r.d.ParentContactId.Id)
})
.fail((_, __, err) => Xrm.Utility.alertDialog(`Something went wrong. Please try again or contact the IT-department.\n\nGuru meditation:\n${err}`))
}, 1000)
}
TypeScript definitions:
declare var Mscrm: Mscrm
interface Mscrm {
LeadCommandActions: LeadCommandActions
}
interface LeadCommandActions {
performActionAfterHandleLeadDuplication: { (returnValue: any): void }
}
declare var Xrm: Xrm
interface Xrm {
Page: Page
SaveMode: typeof SaveModeEnum
Utility: Utility
}
interface Utility {
alertDialog(message: string): void
openEntityForm(name: string, id?: string): Object
}
interface ExecutionContext {
getEventArgs(): SaveEventArgs
}
interface SaveEventArgs {
getSaveMode(): SaveModeEnum
isDefaultPrevented(): boolean
}
interface Page {
context: Context
data: Data
}
interface Context {
getClientUrl(): string
}
interface Data {
entity: Entity
}
interface Entity {
getId(): string
}
declare enum SaveModeEnum {
qualify
}
TypeScript-output:
function OnSave(executionContext) {
var eventArgs = executionContext && executionContext.getEventArgs();
if (!eventArgs || eventArgs.isDefaultPrevented() || eventArgs.getSaveMode() !== Xrm.SaveMode.qualify)
return;
var originalCallback = Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication;
Mscrm.LeadCommandActions.performActionAfterHandleLeadDuplication = function (returnValue) {
originalCallback(returnValue);
RedirectToContact();
};
RedirectToContact();
}
function RedirectToContact(retryCount) {
if (retryCount === void 0) { retryCount = 0; }
if (retryCount === 10)
return Xrm.Utility.alertDialog("Could not redirect you to the contact. Perhaps something went wrong while CRM tried to create it. Please try again or contact the nerds in the IT department.");
setTimeout(function () {
if ($("iframe[src*=dup_warning]", parent.document).length)
return;
var leadId = Xrm.Page.data.entity.getId();
$.getJSON(Xrm.Page.context.getClientUrl() + ("/XRMServices/2011/OrganizationData.svc/LeadSet(guid'" + leadId + "')?$select=ParentContactId"))
.then(function (r) {
if (!r.d.ParentContactId.Id)
return RedirectToContact(retryCount + 1);
Xrm.Utility.openEntityForm("contact", r.d.ParentContactId.Id);
})
.fail(function (_, __, err) { return Xrm.Utility.alertDialog("Something went wrong. Please try again or contact the IT-department.\n\nGuru meditation:\n" + err); });
}, 1000);
}
There is a fully functional and supported solution posted over at our Thrives blog: https://www.thrives.be/dynamics-crm/functional/lead-qualification-well-skip-that-opportunity.
Basically we combine the plugin modification as mentioned by Pawel with a Client Side redirect (using only supported JavaScript) afterwards:
function RefreshOnQualify(eventContext) {
if (eventContext != null && eventContext.getEventArgs() != null) {
if (eventContext.getEventArgs().getSaveMode() == 16) {
setTimeout(function () {
Xrm.Page.data.refresh(false).then(function () {
var contactId = Xrm.Page.getAttribute("parentcontactid").getValue();
if (contactId != null && contactId.length > 0) {
Xrm.Utility.openEntityForm(contactId[0].entityType, contactId[0].id)
}
}, function (error) { console.log(error) });;
}, 1500);
}
}
}

Custom validation not firing client-side

I am writing a custom attribute to require a property in a viewmodel if another property has a specified value.
I used this post for reference: RequiredIf Conditional Validation Attribute
But have been encountering issues with the .NET Core revisions for IClientModelValidator. Specifically, the server side validation works as expected with ModelState.IsValid returning false, and ModelState errors containing my custom error codes. I feel that I am missing something when translating between the differing versions of validator.
The old (working) solution has the following:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] =
(context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
rule.ValidationParameters["desiredvalue"] = DesiredValue is bool
? DesiredValue.ToString().ToLower()
: DesiredValue;
yield return rule;
}
Based on the changes to IClientModelValidator outlined here: https://github.com/aspnet/Announcements/issues/179 I have written the following methods:
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-requiredif", errorMessage);
MergeAttribute(context.Attributes, "data-val-requiredif-dependentproperty", PropertyName);
var desiredValue = DesiredValue.ToString().ToLower();
MergeAttribute(context.Attributes, "data-val-requiredif-desiredvalue", desiredValue);
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
These are being called as expected, and values are properly populated, yet the following JS is ignored. Leaving me to suspect I am missing something between the two.
$.validator.addMethod("requiredif", function (value, element, parameters) {
var desiredvalue = parameters.desiredvalue;
desiredvalue = (desiredvalue == null ? "" : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType === "checkbox" || controlType === "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
$.validator.unobtrusive.adapters.add("requiredif", ["dependentproperty", "desiredvalue"], function (options) {
options.rules["requiredif"] = options.params;
options.messages["requiredif"] = options.message;
});
Any ideas?
EDIT: Just to erase doubt that the server side is working properly and the issue almost certainly lies client side, here is a snip of the generated HTML for a decorated field:
<input class="form-control" type="text" data-val="true" data-val-requiredif="Profession Other Specification is Required" data-val-requiredif-dependentproperty="ProfessionTypeId" data-val-requiredif-desiredvalue="10" id="ProfessionOther" name="ProfessionOther" value="" placeholder="Please Specify Other">
So I had the same setup and same result as the original questioner. By stepping through a project where custom validators were being fired and where they weren't, I was able to determine that when the page is initially loaded, jquery.validate.js attaches a validator object to the form. The validator for the working project contained the key for the custom validator I had created. The validator for the one that did not work was missing that key (which was later added and available at the time I was posting my form).
Unfortunately, as the validator object had already been created and attached to the form without my custom validator, it never reached that function. The key to solving this issue was to move my two JS functions outside of the jQuery ready function, as close to the top of my main script as possible (just after I set my jQuery validator defaults). I hope this helps someone else!
My project is written in TypeScript, so my structure is a bit different but the JavaScript for actually adding the validator remains unchanged.
Here is the code for my "SometimesRequired" validator Typescript class:
export class RequiredSometimesValidator {
constructor() {
// validator code starts here
$.validator.addMethod("requiredsometimes", function (value, element, params) {
var $prop = $("#" + params);
// $prop not found; search for a control whose Id ends with "_params" (child view)
if ($prop.length === 0)
$prop = $("[id$='_" + params + "']");
if ($prop.length > 0) {
var ctrlState = $prop.val();
if (ctrlState === "EditableRequired" && (value === "" || value === "Undefined"))
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add("requiredsometimes", ["controlstate"], function (options) {
options.rules["requiredsometimes"] = options.params["controlstate"];
options.messages["requiredsometimes"] = options.message;
});
// validator code stops here
}
}
Then in my boot-client.ts file (the main file which powers my application's JavaScript), I instantiate a new copy of the validator above (thus calling the constructor which adds the custom validator to the validator object in memory) outside of document.ready:
export class Blueprint implements IBlueprint {
constructor() {
// this occurs prior to document.ready
this.initCustomValidation();
$(() => {
// document ready stuff here
});
}
private initCustomValidation = (): void => {
// structure allows for load of additional client-side validators
new RequiredSometimesValidator();
}
}
As a very simple example not using TypeScript, you should be able to do this:
<script>
$.validator.addMethod("requiredsometimes", function (value, element, params) {
var $prop = $("#" + params);
// $prop not found; search for a control whose Id ends with "_params" (child view)
if ($prop.length === 0)
$prop = $("[id$='_" + params + "']");
if ($prop.length > 0) {
var ctrlState = $prop.val();
if (ctrlState === "EditableRequired" && (value === "" || value === "Undefined"))
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add("requiredsometimes", ["controlstate"], function (options) {
options.rules["requiredsometimes"] = options.params["controlstate"];
options.messages["requiredsometimes"] = options.message;
});
$(function() {
// document ready stuff
});
</script>
The key to solving this issue was to move my two JS functions outside of the jQuery ready function, as close to the top of my main script as possible (just after I set my jQuery validator defaults). I hope this helps someone else!
Credit goes to #Loni2Shoes

Exist stringByEvaluatingJavaScriptFromString for Android

Exist stringByEvaluatingJavaScriptFromString method in Android like Iphone?.
Not with a simple code like this javascript:wave(). But with a complex Java Script function.
Thanks
You can use loadUrl instead,eg:
webView.loadUrl("javascript:PerformSimpleCalculation()");
It's effect similar to stringByEvaluatingJavaScriptFromString.The API description is not clear.
stringByEvaluatingJavaScriptFromString is a private method in Android API. But it is really useful.
You could retrive this API via Java reflection:
Method stringByEvaluatingJavaScriptFromString , sendMessageMethod;;
Object webViewCore , browserFrame;
private boolean hasIntercepted = true;
Object webViewObject = this;
Class webViewClass = WebView.class;
try {
Field mp = webViewClass.getDeclaredField("mProvider");
mp.setAccessible(true);
webViewObject = mp.get(this);
webViewClass = webViewObject.getClass();
Field wc = webViewClass.getDeclaredField("mWebViewCore");
wc.setAccessible(true);
webViewCore = wc.get(webViewObject);
if (webViewCore != null) {
sendMessageMethod = webViewCore.getClass().getDeclaredMethod("sendMessage", Message.class);
sendMessageMethod.setAccessible(true);
Field bf= webViewCore.getClass().getDeclaredField("mBrowserFrame");
bf.setAccessible(true);
browserFrame = bf.get(webViewCore);
stringByEvaluatingJavaScriptFromString = browserFrame.getClass().getDeclaredMethod("stringByEvaluatingJavaScriptFromString", String.class);
stringByEvaluatingJavaScriptFromString.setAccessible(true);
}
hasIntercepted = true;
} catch (Throwable e) {
hasIntercepted = false;
}
Then to use this method:
Object argument =(Object) ((String)"window.hitTest("+event.getX()+ ","+event.getY()+")");
try {
Object _tagName = stringByEvaluatingJavaScriptFromString.invoke(browserFrame, argument);
String tagName =(String)_tagName;
} catch (Exception e) {
e.printStackTrace();
}

Call C#.net method in Javascript

I Have Method in Code behind(C#) and Want to call this method inside the javascript.
My Code in C#
private void StatusSet()
{
List<StatusHandler> iListStatus = new List<StatusHandler>();
iListStatus.Add(new StatusHandler('A', "Active"));
iListStatus.Add(new StatusHandler('I', "InActive"));
iListStatus.Add(new StatusHandler('L', "All"));
if (hdnMode.Value == "i")
{
ddlStatus.DataSource = iListStatus.Take(2);
}
else
{
ddlStatus.DataSource = iListStatus.Take(3);
if (lnkBtnUpdate1.Visible == true)
{
ddlStatus.DataSource = iListStatus.Take(2);
}
}
}
Javascript :
function GetMode(modeIndex) {
if (modeIndex == 'i') {
StatusSet(); //How to Call in Javascript
}
}
You can't call this directly from javascript.
You must use Ajax.
EDIT:
Here you can see how to return a list as a JSON: asp.net web forms json return result
Here you can see how to populate dropdown list: jQuery: Best practice to populate drop down?

Categories

Resources