Passing Object Reference to JS Interop in Blazor WASM - event-handling

I have been working on group Blazor WASM project with a server side API. As per requirement I have integrated Calendly API for event scheduling.
Inside a card component I have embedded the following code provided from their website:
<div class="calendly-inline-widget" data-url="https://calendly.com/my-app/my-event" style="min-width:320px;height:630px;"></div>
<script type="text/javascript" src="https://assets.calendly.com/assets/external/widget.js" async></script>
Calendly uses window.postMessage() to post events to the parent window so I invoke the following code on component initialization to 'intercept' the events and make some calls to Calendly through my server side logic:
function isCalendlyEvent(e) {
return e.data.event &&
e.data.event.indexOf('calendly') === 0;
};
window.addEventListener(
'message',
function (e) {
if (isCalendlyEvent(e)) {
if (e.data['event'] == 'calendly.event_scheduled') {
let data = {
eventUri: e.data.payload.event.uri,
inviteeUri: e.data.payload.invitee.uri
};
fetch("https://localhost:5001/CoachSessions/AddSession", {
method: "POST",
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
}).then(res => {
//TODO Add Callback from JS Event listener to Blazor Instance Method
if (res.status == 200) {
DotNet.invokeMethodAsync('MyApp.ClientSide', 'AddedSession');
}
});
}
}
}
);
My problem and question is how can I pass Object Reference when an event is triggered so I could make an instance method callback to my Blazor component?
In the code above I have tried calling a static method and it works but it requires everything else to be static so its not ideal to say the least.
I have also tried random ideas such as passing object references as an data attribute and hooking the object reference to the window object but to no avail.
To clarify, I need this so I can close some modals after a successful scheduling and to limit the user from scheduling more events of the same type.

Related

Listen to keyboard input in the whole Blazor page

I'm trying to implement a Blazor app that listens to keyboard input all the time (some kind of full screen game, let's say).
I can think of a key down event listener as a possible implementation for it, since there's not really an input field to auto-focus on.
Is there a better solution to just react to key-presses in any part of the screen?
In case that's the chosen one, how can I add an event listener from a client-side Blazor app? I've failed trying to do so by having a script like this:
EDIT: I modified a little bit the code below to actually make it work after fixing the original, key mistake that I was asking about.
scripts/event-listener.js
window.JsFunctions = {
addKeyboardListenerEvent: function (foo) {
let serializeEvent = function (e) {
if (e) {
return {
key: e.key,
code: e.keyCode.toString(),
location: e.location,
repeat: e.repeat,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
metaKey: e.metaKey,
type: e.type
};
}
};
// window.document.addEventListener('onkeydown', function (e) { // Original error
window.document.addEventListener('keydown', function (e) {
DotNet.invokeMethodAsync('Numble', 'JsKeyDown', serializeEvent(e))
});
}
};
index.html
<head>
<!-- -->
<script src="scripts/event-listener.js"></script>
</head>
Invoking it through:
protected async override Task OnAfterRenderAsync(bool firstRender)
{
await jsRuntime.InvokeVoidAsync("JsFunctions.addKeyboardListenerEvent");
}
and having the following method trying to receive the events:
using Microsoft.AspNetCore.Components.Web;
using Microsoft.JSInterop;
namespace Numble;
public static class InteropKeyPress
{
[JSInvokable]
public static Task JsKeyDown(KeyboardEventArgs e)
{
Console.WriteLine("***********************************************");
Console.WriteLine(e.Key);
Console.WriteLine("***********************************************");
return Task.CompletedTask;
}
}
I manage to get the script executed, but I'm not receiving any events.
The name of the event is keydown, not onkeydown.

Events provider is deprecating. Using Redux or Observables for state in ionic apps

I've been using events in my ionic application, where i subscribe in one page, and publish the event in the other page. Now I see a warning that Events are going to be changed with Observables and Redux state and effect.
I was using Events mainly to call for component function changes outside it, so I had a components for example:
Component1.ts
this.events.subscribe('event:addValue1', (data: any) => {
this.valueName = 'VALUE1';
});
this.events.subscribe('event:addValue2', (data: any) => {
this.valueName = 'VALUE2';
});
and than outside this component I was calling the publish methods from any page, like:
Page1.ts
this.events.publish('event:addValue1');
Page2.ts
this.events.publish('event:addValue2');
By this i was able to change the data (this.valueName) outside the Component1.ts from any other page, simply by publishing the desired event.
I know that this might not sound or be right approach, but It was the only way I was doing changes to my Component1.ts outside it from any page.
I have now changed this and just put separate functions and than i access them via ViewChild component name like
#ViewChild('component') component: any;
....
this.component.functionAddValue1().
and additionally I send additional params via Angular NavigationExtras if i need to calculate and call some function from the Component1.ts, lets say if I navigate to some route.
Before this I was just calling the events.publish and I was able to make the changes to the Component1.ts on the fly.
Create event service.
In the EventService.ts:
export class EventService {
private dataObserved = new BehaviorSubject<any>('');
currentEvent = this.dataObserved.asObservable();
constructo(){}
publish(param):void {
this.dataObserved.next(param);
}
}
For publishing the event from example page1:
constructor(public eventService:EventService){}
updatePost(value){
this.eventService.publish({name:'post:updated',params:value});
}
In page 2:
constructor(public eventService:EventService){
eventService.currentEvent.subscribe(value=>{
if(value.name=='post:updated'){
//get value.name
}else if(value.name=='another:event'){
//get value or update view or trigger function or method...
}
// here you can get the value or do whatever you want
});
}

Vue Js fetch data onclick and append inside a modal

I have two components, one is parent component and another one is child component which is a modal popup.
Parent component
<parent-component>
<!-- this is a modal popup -->
<child-component v-bind:message="childMessage"></child-component>
Open model
</parent-component>
<script>
export default {
data: function() {
return {
childMessage:{}
}
},
methods:{
openModal: function(id){
axios.get('api/message/'+id)
.then(response => {
this.childMessage = response.data;
})
.catch(error => {
console.log(error);
})
this.showModal = true
}
}
}
</script>
Child component
<!-- this is a popup modal -->
<child-component>
<h1>{{ message.title }}</h1>
</child-component>
<script>
export default {
props:{
message: {},
}
</script>
In parent component, I trigger the modal and request ajax at the same time.
And I can pass the ajax data to child component correctly.
But if I open the console, there is an error
Cannot read property 'title' of undefined
(Although I can see the data is working fine and it's already in html page)
It seems the appending data {{ childMessage.title }} run first (before the ajax request).
1 - How can I append the data correctly, probably after the ajax request.
2 - Do I need to check the condition for undefined value?
I don't see where you use showModal but I suppose it's a sort of switch to display or not the child-component. If it's the case the error can come from the fact that you set showModal to true just after the call to the API. But this call is asynchronous you should probably move this.showModal = true in the success callback under this.childMessage = response.data;.
If you do that the message prop will be initialize at the moment the child component is rendered.
Also pay attention to your prop type, as #ittus mention message seems to be a String according to the default value in the child-component but you use it like an object in the template.
In this case, you have to check the condition for undefined value because child component is being rendered before the message API call ends. You can do it in this way,
<child-component>
<h1 v-if = "message">{{ message.title }}</h1>
</child-component>

How do I know that I'm still on the correct page when an async callback returns?

I'm building a Metro app using the single-page navigation model. On one of my pages I start an async ajax request that fetches some information. When the request returns I want to insert the received information into the displayed page.
For example:
WinJS.UI.Pages.define("/showstuff.html", {
processed: function (element, options) {
WinJS.xhr(...).done(function (result) {
element.querySelector('#target').innerText = result.responseText;
});
}
};
But how do I know that the user hasn't navigated away from the page in the meantime? It doesn't make sense to try to insert the text on a different page, so how can I make sure that the page that was loading when the request started is still active?
You can compare the pages URI with the current WinJS.Navigation.location to check if you are still on the page. You can use Windows.Foundation.Uri to pull the path from the pages URI to do this.
WinJS.UI.Pages.define("/showstuff.html", {
processed: function (element, options) {
var page = this;
WinJS.xhr(...).done(function (result) {
if (new Windows.Foundation.Uri(page.uri).path !== WinJS.Navigation.location)
return;
element.querySelector('#target').innerText = result.responseText;
});
}
};
I couldn't find an official way to do this, so I implemented a workaround.
WinJS.Navigation provides events that are fired on navigation. I used the navigating event to build a simple class that keeps track of page views:
var PageViewManager = WinJS.Class.define(
function () {
this.current = 0;
WinJS.Navigation.addEventListener('navigating',
this._handleNavigating.bind(this));
}, {
_handleNavigating: function (eventInfo) {
this.current++;
}
});
Application.pageViews = new PageViewManager();
The class increments a counter each time the user starts a new navigation.
With that counter, the Ajax request can check if any navigation occurred and react accordingly:
WinJS.UI.Pages.define("/showstuff.html", {
processed: function (element, options) {
var pageview = Application.pageViews.current;
WinJS.xhr(...).done(function (result) {
if (Application.pageViews.current != pageview)
return;
element.querySelector('#target').innerText = result.responseText;
});
}
};

Re-POST-ing a form using Ajax, for back/fwd history, in jQuery Mobile

I'm investigating the suitability of the jQM history management for my organization's mobile web site. Part of our requirement is that when a user posts a form, it posts using Ajax. When a user taps "back" and then "forward" browser buttons, we need to re-post the form using Ajax again. Here's a diagram:
[home.php: post to new.php] =>
[new.php: post to list.php] =>
[from list.php, click back twice to home.php] =>
[* home.php: click fwd, does a GET request to new.php *] =>
[* new.php: click fwd, does a GET request to list.php *]
[*] not the behavior I want. I want to re-POST data from steps 1 and 2
During steps 4 and 5, I want to intercept the jQM execution at the "pagebeforeload" event (seems like the right place), and modify the data.options.data (to include the serialized post data) and data.options.type (to POST) properties of the data object which is passed to the event handler. Then I'd let the pageload process move on with the modified values. This, I think, would give me the behavior I want.
So far, I see that I can intercept the pagebeforeload event and modify the values with the below event handler:
$(document).bind("pagebeforeload",
function( e, data ) {
console.log(data.toPage);
console.log(data.options);
data.options.type = "POST";
data.options.data = "edit=Hanford";
}
);
But the missing piece is, how can I "store" the information in steps 1 and 2, such that when I intercept the pagebeforeload event in 4 and 5, I can know to modify the "type" and "data" properties properly.
I've made a jquery/browser History prototype to illustrate the behavior I would like to get from jQuery Mobile. The JavaScript is:
$(window).load(function() {
//listen to the history popstate event
$(window).bind('popstate',
function(e) {
if (e.originalEvent.state && e.originalEvent.state.type && e.originalEvent.state.type == "post") {
//do an ajax post based on what is stored in history state
$.post(e.originalEvent.state.path,
e.originalEvent.state.data,
function(data) {jaxer(data,'POST')});
} else if (e.originalEvent.state !== undefined){
//do an ajax get
$.get(location.pathname,
function(data) {jaxer(data,'GET')});
}
});
//add event listeners
addClickHandler();
});
function jaxer(data, msg) {
if (msg) {console.log(msg+' request made using ajax');}
$('#slider')[0].innerHTML = data;
addClickHandler();
}
//hijax hyperlinks and form submissions
function addClickHandler() {
$('a').click(function(e) {
history.pushState({
path: this.path
},
'', this.href);
$.get(this.href,
function(data) {jaxer(data,'GET')});
e.preventDefault()
});
$('form').submit(function(e) {
var formData = $(this).serialize();
console.log('saving serialized form data into historyState: '+formData);
history.pushState({path: this.action, data: formData, type: "post"},'',this.action);
$.post(this.action,
$(this).serialize(),
function(data) {jaxer(data,'POST')});
e.preventDefault();
});
//add the form submit button to the form as a hidden input such that it will get serialized
$('form input[type="submit"]').click(function() {
$(this.form).append("<input type='hidden' name='" + this.name + "' value='" + this.value + "'/>");
});
console.log("event handlers added to DOM");
};
When a user-initiated POST occurs, I put the serialized POST data into the history's state object. Then, when a popstate event occurs, I can query the state object for this information. If I find it, then I can perform an ajax POST. I would like to get the same functionality within the jQuery Mobile history handling. That way I can take advantage of the page change animations, etc.