How to bind to document events with Blazor - .net-core

I am writing a simple snake game with Blazor, but I can't figure out how to bind to document events. I know that it is possible to bind events on different elements such as div or input. Example: <input onkeypress="#KeyPressInDiv"/>, where the handler is public void KeyPressInDiv(UIKeyboardEventArgs ev) {...}.
I suppose that there should be some equivalent to the JavaScript method document.onkeydown = function (evt) {}. I have found two approaches for working around this problem:
Use JavaScript for binding and invoke Blazor code (taken from https://github.com/aesalazar/AsteroidsWasm):
document.onkeydown = function (evt) {
evt = evt || window.event;
DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyDown', evt.keyCode);
//Prevent all but F5 and F12
if (evt.keyCode !== 116 && evt.keyCode !== 123)
evt.preventDefault();
};
document.onkeyup = function (evt) {
evt = evt || window.event;
DotNet.invokeMethodAsync('Test.ClientSide', 'JsKeyUp', evt.keyCode);
//Prevent all but F5 and F12
if (evt.keyCode !== 116 && evt.keyCode !== 123)
evt.preventDefault();
};
... and in C# implement a static class with methods marked by [JSInvokable] and events. This works, but leads to an extreme delay on every key press.
It is possible to add an input tag and bind to its events. This works much faster than the previous approach, but it seems like a hack rather then a solution. Also, we are not able to listen for some actions, such as Up/Down Arrow.
Is there a direct way to bind to document events from Blazor?
Update 1: I created a simple project to better explain what I want to achieve: https://github.com/XelMed/BlazorSnake
There are 3 implementations of Snake:
Pure JS - this has the expected behavior
Using JS with Blazor - invoke a Blazor function from JS code with a JsInterop
Using input tag - bind to events on an input tag to control the snake

Perhaps add a event listener to the document using JsInterop and assign a anonymus function to the event which calls your C# method with the even parameters.
For example your JsInterop.js:
document.addEventListener('onkeypress', function (e) {
DotNet.invokeMethodAsync('Snake', 'OnKeyPress', serializeEvent(e))
});
with serializeEvent being as follos to avoid some quirkiness:
var serializeEvent = function (e) {
if (e) {
var o = {
altKey: e.altKey,
button: e.button,
buttons: e.buttons,
clientX: e.clientX,
clientY: e.clientY,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
movementX: e.movementX,
movementY: e.movementY,
offsetX: e.offsetX,
offsetY: e.offsetY,
pageX: e.pageX,
pageY: e.pageY,
screenX: e.screenX,
screenY: e.screenY,
shiftKey: e.shiftKey
};
return o;
}
};
in your C# code you would have:
[JSInvokable]
public static async Task OnMouseDown(UIMouseEventArgs e){
// Do some stuff here
}

I had the same requirement as you, and I managed to wire up document events (e.g. keydown) to my Razor methods using invokeMethodAsync, but then I found that I missed out on the automatic DOM diffing and updating that Blazor provides if the Razor method changes some state which is bound to an HTML element (e.g. controls the visibility of a div element).
It seems (from my limited understanding of Blazor) that normally Blazor returns whatever state data is necessary to update the DOM, in the return data of a WebAssembly method.
But if you use invokeMethod or invokeMethodAsync from JavaScript, you have to manage returning and consuming this data yourself, but that might be problematic because your updates might conflict with Blazor's view of the DOM state.
So I came up with a hacky approach of generating a hidden button in my Razor view, e.g.:
<button id="arrow-left-button" #onclick="HandleArrowLeftPress"></button>
And then on the JavaScript side, I wired up a document event which finds that button by Id and calls .click() on it:
document.getElementById('arrow-left-button').click();
I know this seems truly awful, but it worked for what I was doing, which was using the arrow keys to move an absolutely positioned element on screen.
If anyone knows a cleaner way (like how to force an update of a Razor view by its name from the JavaScript side, in the handler callback), please let me know.

I encountered similar situation and use the following approach as a workaround. You add event listener to a div tag, then set focus to that div such that your key presses can be captured.
<div tabindex="0" #ref="container" #onkeydown="onKeyDown" id="container">
//your snake game markup
</div>
#code {
ElementReference container;
protected override async Task OnAfterRenderAsync(bool firstRender) {
await container.FocusAsync();
}
private void onKeyPress(KeyboardEventArgs obj) { //your code here. }
}
You may need to remove the focus border of the div tag:
#container:focus-visible {
outline: none;
}
#container:focus {
outline: none;
}

To extend a bit on the answer by Blightbuster
For me at least the returned serialized event object would not cast to its .NET counterpart (KeyboardEventArgs in my case). I had to stringify it first
var serializeEvent = function (e) {
if (e) {
var o = {
//assign properties here
//we can´t just stringify e since it has circular references
};
return JSON.stringify(o);
}
};
And then deserialize in the .NET method called by the JSRuntime
using System.Text.Json;
T deserialized = JsonSerializer.Deserialize<T>(
s,
new JsonSerializerOptions() {
PropertyNameCaseInsensitive = true
});
Also, if you are lazy like me and don´t want to look up the properties you need from the event, you can look up the property names in .NET before calling your js method
var propertyNames = typeof(T)
.GetProperties()
.Where(x => x.CanWrite)
.Select(x => $"{x.Name[..1].ToLower()}{x.Name[1..]}")
.ToArray();
Then pass those to js and build your event object
let o = {};
propertyNames.forEach(propertyName => {
o[propertyName] = e[propertyName];
});
var serialized = JSON.stringify(o);
Finally I did not want to make a static method with the [JSInvokable] attribute, so I just made a little wrapper.
class JSInvokableWrapper<T>
{
Func<T, Task> Func { get; }
public JSInvokableWrapper(
Func<T, Task> func)
{
Func = func;
}
[JSInvokable]
public async Task Invoke(T argument)
{
await Func.Invoke(argument);
}
}
Use that to wrap whatever func you want js to execute and turn the wrapper into a DotNetObjectReference (Maybe in some extension method for the IJSRuntime)
var dotNetObjectReference = DotNetObjectReference
.Create(new JSInvokableWrapper<T>(func));
And call its Invoke method from js
dotNetObjectReference.invokeMethodAsync('Invoke', param);

You can bind the event directly to a C# method, just using the event tag that you need (onkeyup/onmousemove ....) .
#page "/"
<div>
<input type="text" onkeyup=#KeyUp />
</div>
#functions {
void KeyUp(UIKeyboardEventArgs e)
{
Console.WriteLine(e.Key);
}
protected override Task OnInitAsync()
{
return Task.CompletedTask;
}
}

Related

How to react to javascript CustomEvent from Blazor webassembly component

I have a Canvas element which has some custom functionality written in Javascript. I'm trying to create a wrapper for this functionality in a Blazor Webassembly component.
The Javascript code generates custom events using the dispatchEvent method:
const event = new CustomEvent("uvrectadded", {
bubbles: true,
detail: { uvrect: uvr }
});
const result = this.canvas.dispatchEvent(event);
How to I listen for this event in a Blazor serverside component? I have tried the following:
<canvas id="map-canvas" #uvrectadded="OnUVRectAdded"></canvas>
#code{
private void OnUVRectAdded(EventArgs e)
{
Console.WriteLine("Blazor: ONUVRectAdded");
}
}
In the code above, the directive is not compiled, but I can't find out how to register an event directive.
Aside: I have also tried raising a change event from the Javascript component, and this does get registered by the Blazor code with an #onchange directive, however, an exception is thrown saying the system cannot deserialize the event.
In Blazor, you can register a directive using an EventHandlerAttribute like this:
[EventHandler("onuvrectadded", typeof(EventArgs))]
public static class EventHandlers
{
}
However, I am having some issues deserializing the detail property of the CustomEvent.
Im struggling too. I can see the JS event being raised (have added a JS listener to check is actually raised).
I have registered the event, and defined the C# part of it as well.
[EventHandler("oncustomjsevent", typeof(CustomJSEventArgs), enableStopPropagation: true, enablePreventDefault: true)]
public static class EventHandlers
{
// This static class doesn't need to contain any members. It's just a place where we can put
// [EventHandler] attributes to configure event types on the Razor compiler. This affects the
// compiler output as well as code completions in the editor.
}
public class CustomJSEventArgs : System.EventArgs
{
// Data for these properties will be supplied by custom JavaScript logic
public string EventData { get; set; }
}
But cant get Blazor to see the event anywhere. Have tried adding to divs, buttons, labels, etc still doesnt work., eg.
<Button #oncustomjsevent="OnLinkClick" />
private void OnLinkClick(CustomJSEventArgs eventArgs)
{
throw new InvalidCastException(eventArgs.EventData);
var x = 1;
}

correctly setting up and using angular with asp.net-mvc

I'm trying to learn the correct way of using angular with asp.net-mvc. Right now what I'm doing is letting the asp.net engine render my views, but I'm not passing any information to the view as a model. I'm using ng-init to call a scope function to retrieve data as json.
for example my index view's get action method is as follows:
public ActionResult Index()
{
return View();
}
and I have another controller action method that angular calls:
public ActionResult getMembersList()
{
List<CommunityMember> cmList = new List<CommunityMember>();
var members = db.Member.OrderBy(x => x.FirstName).ToList();
foreach (var m in members)
{
CommunityMember cm = new CommunityMember(m);
cmList.Add(cm);
}
return Json(cmList, JsonRequestBehavior.AllowGet);
}
(as an aside, should I avoid this foreach loop? it just creates a list of my CommunityMember viewmodel)
and the angular function looks like this:
$scope.getMembersList = function () {
$http.get("/Members/getMembersList")
.success(function (data) {
$scope.membersList = data;
}).error(function (data) {
console.log(data);
});
};
also all my code for angular is currently in an angular controller:
var myApp = angular.module("myApp", []);
myApp.controller('myIndexViewModel', function myIndexViewModel($scope, $http, $compile) {
//scope functions/variables
//http calls
//etc..
}
I just feel like this isnt the best approach and want to know if theres a better way to do this. Should I be loading data the way I am through ng-init then using the injected $http to access the server?
should I put all the $http calls in a different place?
thanks for any help!
Inside foreach loop what you are making is converting a list of objects to another list of objects.This is so called mapping(projection) in functional programming and there is already a linq method to do that. Use Select,it is more concise.
var members = db.Member.OrderBy(x => x.FirstName)
.Select(x=>new CommunityMember(){
Name=x.Some,
Age=x.Age
}).ToList();
The code placed in the controller is not so good. Responsibility of the controller should be data and view not to call services using $http. Move the $http calls to a custom service that implements promise pattern to notify the controller.Now controller is free from any logic other than updating data that is on the UI.
Try to avoid using Razor for data rendering and keep your front view decocupled from Razor. Js ,html views can be hosted anywhere.You dont have to keep it .Net project too.Make your server side behave as Api layer for your angular application.So make your views static and let angular decide functionality.
Indeed ng-init isn't the best way of getting data from your backend. Instead, you have two other options.
The first one is to resolve getMembersList inside your route.
The second one is to resolve getMembersList inside your controller, update your scope and then hide your loader.
Option 1 (using UI-router) :
state('members', {
url: '/members',
templateUrl: 'someTemplateURL',
controller: function(members) {
//Do something here
},
resolve: {
members: function($http) {
return $http.get("/Members/getMembersList")
}
}
})
Option 2 (Inside Controller using controllerAs syntax and ES6 fat arrow) :
memberModule.controller('membersCtrl', function ($http) {
this.loader = true;
$http.get("/Members/getMembersList")
.success((data) => {
this.membersList = data;
this.loader = false;
//Your view will pick up the change after the next digest cycle
})
}

Is there a way to exclude a Client from a Clients.method call in SignalR?

I am evaluating SignalR (which happens to be used with Knockoutjs) to see if we can use it to notify clients of concurrency issues. Basically user "a" saves a record and users "b,c,d,e,f,g" are notified. I basically have an example working that notifies all clients. So I think I am almost there.
I came across this link and it lead me on the current path that I am on. I have also been looking at the documentation on Github.
Basically I want to exclude the a single client from the Clients.method() call. I dont see a way to loop through the clients and check the ClientId. The only other I can see to accomplish this is to maybe look at using the groups to keep track of it, but that seemed a little cumbersome, but I was having issues with that as well.
public class TicketHub : Hub
{
static int TotalTickets = 10;
public void GetTicketCount()
{
AddToGroup("ticketClients");
Clients.setTicketCount(TotalTickets);
}
public void BuyTicket()
{
if (TotalTickets > 0)
TotalTickets -= 1;
RemoveFromGroup("ticketClients");
// This will call the method ONLY on the calling client
// Caller.updateTicketCountWithNotification(TotalTickets);
// This will call the method on ALL clients in the group
Clients["ticketClients"].updateTicketCountNotify(TotalTickets);
AddToGroup("ticketClients");
Caller.updateTicketCountDontNotify(TotalTickets);
}
}
javascript code:
<script type="text/javascript">
$(document).ready(function () {
var test = $.connection.test;
$("#btnTest").click(function () {
test.testMethod();
});
test.show = function (text, guid) {
if (guid != test.guid) //notify all clients except the caller
alert(text);
};
$.connection.hub.start(function () { test.start(); });
});
</script>
Class :
public class Test : Hub
{
public void Start()
{
Caller.guid = Guid.NewGuid();
}
public void TestMethod()
{
Clients.show("test", Caller.guid);
}
}
If you want to exclude the caller from the call to the client side method you can use:
Clients.Others.clientSideMethod();
There is also Clients.AllExcept(...) that allows excluding certain people.

ASP.NET MVC 2.0 Custom Client Validation

I am trying to make a validator that will make sure that at least 2 items are selected. The validator works correctly on the server side but the client side code never gets executed.
Here is the code:
Sys.Mvc.ValidatorRegistry.validators["country"] = function (rule) {
var min = rule.ValidationParameters["min"];
return function (value, context) {
if (value >= min) return true;
return rule.ErrorMessage;
};
};
And here is the validator code:
public class CountryValidator : DataAnnotationsModelValidator<CustomValidations.CountryAttribute>
{
private int _minimum;
private string _message;
public CountryValidator(ModelMetadata metadata, ControllerContext context, CustomValidations.CountryAttribute attribute) : base(metadata,context,attribute)
{
_minimum = attribute.Minimum;
_message = attribute.ErrorMessage;
}
public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
{
var rule = new ModelClientValidationRule()
{
ErrorMessage = _message,
ValidationType = "country"
};
rule.ValidationParameters.Add("min", _minimum);
return new[] { rule };
}
}
I have even registered the validation adapter in global.asax file:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(AgeAttribute), typeof(AgeValidator));
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(CountryAttribute),typeof(CountryValidator));
}
I am thinking that the validator only works with the elements that have a value property like textboxes etc.
UPDATE 1:
EnableClientValidation is invoked correctly and all the required JS files are included in the project. It seems like I need to attach the onblur to the context. I will try that and post the results.
<% =Html.EnableClientValidation(); %> needs to be in your view somewhere. Also make sure you reference MicrosoftAjax.js and MicrosoftMvcValidation.js in the same view (before your js function).
Either your missing MicrosoftMvcAjax.js or you need to implement your custom validation in jQuery as described on Mr. Haack's website http://haacked.com/archive/2009/11/19/aspnetmvc2-custom-validation.aspx.
I think it is because the default validation is invoked on the onblur event of the input textbox. And for a listbox this event was not being thrown.

Flex, Flexunit: How to test that an event is dispatched twice?

I'm testing some event dispatch code in a Flex app, using FlexUnit's addAsync method for testing that events are dispatched. Great so far, I can ensure that at least one event was fired. However, I want to be a bit more detailed; I want to ensure that exactly the set of events I'm expecting are dispatched. Is there a useful test pattern (or, even, different test framework -- I'm flexible!) to accomplish this?
I tried this code, but it doesn't seem to get invoked the second time:
protected function expectResultPropertyChange(event: Event, numberOfEvents: int = 1): void {
trace("Got event " + event + " on " + event.target + " with " + numberOfEvents + " traces left...");
assertTrue(event.type == ResponseChangedEvent.RESPONSE_CHANGED);
if (numberOfEvents > 1) {
event.target.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, addAsync(expectResultPropertyChange, 1000, numberOfEvents - 1));
}
}
public function testSomething(): void {
requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, addAsync(expectResultPropertyChange, 1000, 2));
requiredQuestion.responseSelected("1", true);
requiredQuestion.responseSelected("2", true);
}
In response to the comment...
What if the event is dispatched
directly? responseSelected doesn't
trigger an asynchronous event on a
composite object, it simply dispatched
the RESPONSE_CHANGED event itself
directly. I'm not seeing how this
approach can be mocked using your
method. Mind you, I'm fuzzy on the
mock testing practice as-is, so I'm
probably missing a simple solution
here.
..in that case you don't need to use a mock or addAsync. Something like this will do:
public function testSomething(): void
{
var requiredQuestion : RequiredQuestion = new RequiredQuestion();
var callCount : int = 0;
requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, function(event : ResponseChangedEvent)
{
callCount++;
});
requiredQuestion.responseSelected("1", true);
requiredQuestion.responseSelected("2", true);
assertEquals(2, callCount);
}
This is going to be a high level example of how a similar problem could be solved using a mocked out object of whatever it is that's doing the asynchronous call. Obviously i can't see your code so i can't give you a precise example.
So, as i said in the comment, you can mock out a dependency in a class to fake asynchronous calls so that they become synchronous. Take the below class
public class RequiredQuestion extends EventDispatcher
{
private var someAsynchronousObject : IAsynchronousObject;
public function RequiredQuestion(someAsynchronousObject : IAsynchronousObject = null)
{
someAsynchronousObject = someAsynchronousObject || new AsynchronousObject();
someAsynchronousObject.addEventListener(Event.COMPLETE, asyncCallComplete);
}
public function responseSelected(id : String, flag : Boolean) : void
{
//Will asynchronously fire the Event.COMPLETE event
someAsynchronousObject.startAsynchrounsCall();
}
protected function asyncCallComplete(event : Event) : void
{
dispatchEvent(new ResponseChangedEvent(ResponseChangedEvent.RESPONSE_CHANGED));
}
}
So by default you are using the concrete class that you want to use unless someAsynchronousObjec is injected into the class via the constructor. AsycnhronousObject probably has it's own unit tests or it's in an external class so you don't really want, or need to be testing its functionality. What you can now do is create a mock object that implements IAsynchronousObject that can be used to fake its behavior. Using the ASMock framework the test could look something like this:
public function testSomething(): void
{
var mockIAsycnhronousObject : IAsynchronousObject =
IAsynchronousObject(mockRepository.createStrict( IAsynchronousObject));
SetupResult.forEventDispatcher(mockIAsycnhronousObject);
SetupResult.forCall(mockIAsycnhronousObject.startAsynchronousCall())
.dispatchEvent(new Event(Event.COMPLETE)); // all calls to the startAsynchronousCall method and dispatch the complete event everytime it's called.
mockRepository.replayAll();
var requiredQuestion : RequiredQuestion = new RequiredQuestion(mockIAsycnhronousObject);
var callCount : int = 0;
requiredQuestion.addEventListener(ResponseChangedEvent.RESPONSE_CHANGED, function(event : ResponseChangedEvent)
{
callCount++;
});
requiredQuestion.responseSelected("1", true);
requiredQuestion.responseSelected("2", true);
assertEquals(2, callCount);
mockRepository.verifyAll();
}
This is just one example of how mocking can help you unit tests. There's a whole wealth of info out there on mocking although it is still very new to ActionScript (released in December). ASMock is based on the .net Rhino mocks so searching for Rhino mocks should throw up a lot more results if you need help.
Definitely a different way of thinking but once you get into it you tend to wonder how you got by in unit testing without them.

Resources