I have an angular application which is kind of the main application that hosts sub application inside it. The sub applications are also angular applications. The way that I load sub application is through Iframes.
The sub-applications are shown as a list and when I click on an tab, that application is loaded. When I am making some changes to the data in an application and if I click on another sub-tab, I wanted to show a warning message saying " changes will be lost". I am able to achieve it by using beforeunload event as below. I can check to see if there are any unsaved changes and show the warning popup.
#HostListener('window:beforeunload', ['$event'])
unloadNotification($event: any) {
if (**my logic here) {
$event.returnValue =true;
}
}
The only problem with this is, when I click the other sub-tab, the host application highlights that sub-tab and then the warning pop-up is shown. If I click on stay button on the popup, I am able to be on the sub-tab I want but on the host application the other sub-tab is highlighted. So I am trying to see a way to not highlight the other tab if I want to stay on the current sub-tab. Something before beforeunload.
My understanding is you have to update an existing project that's been constructed in such a way that the beforeunload event is employed to achieve something similar to the Angular feature called route guards. You do or don't load the selected iframe depending on your logic implemented with the help of beforeunload event. You don't seem to favor changing this implementation that's why seeking a solution that covers your requirement applying a similar approach like another window event.
If my understanding is correct, you need to refactor this implementation so that the "guarding" happens depending on the inner workings of the "host application" where it actually has to be in the first place. The issue you have arises because by the time the iframe's unload event is canceled, host application's canceled tab gets already selected.
In short, this seems to be handled in your tab's selection event.
My answer may or may not propose a solution you would accept, since we don't have all the details like which component suit you use, I can only present a pseudo solution:
Inside the component where your navigation tab's ui logic takes place:
onTabSelected(selectedIndex) {
if (..your logic here) {
loadIframe(selectedIndex);
highlightTab(selectedIndex);
}
}
There is no such event as before beforeUnload. Two applications i.e. host and child can communicate via postMessage. so in beforeUnload event you can send a postMessage to child application to highlight existing tab. for eg:-
let say you had reference of child window in variable name child1.
child1.postMessage('{'tabId': 'tab1'}');
Your child application can receive this message and highlight the tab with identifier tab1.
Maybe we can create a communication system using the window.top object for example let's say that in the child apps you assign some id.
Then in the main app which hosts the others you create a subobject to window.top.appsMap = {};
Then from each child app you update a boolan value for example
window.top.appsMap['applicationId'] = hasChangesOrNot
Then from the top app where you change tabs you can check if the current opened tab app id has changes and not switch the tab but just try to unload the current iframe, it's not the best solution but if should work you can maybe also set some communication channel backwards which would trigger from the main app which hosts the others the save from the child app
In child app
const origin = this.getCurrentHostOrigin();
if (cancelSwitchTab) {
window.parent.postMessage({}, origin);
}
In host app:
this.renderer.listen(this.windowsRef.nativeWindow, 'message', event => {
const message = event.data;
*** your logic to revert highlight to current tab ***
});
Related
I'm trying to debug the integration between my app and Stripe's Elements component library. Everything works fine in sandbox mode, but we ran into a problem on production in the 3D Secure authentication process. This involves loading an iframe, into our app, that contains a form from the credit card's issuer (usually via a technology partner, like Arcot).
The form loads correctly and its buttons are working as expected, but the element (for a SMS one time code) is not behaving. Every time I click on the input, something is immediately pushing the focus back to the element of the iframe. This makes it impossible to type anything in, since by the time I touch a key, the input is not in focus. For reference, it is possible to change the input's value using document.getElementById('enterPIN').value = '123456';
I'm not sure if my app is triggering focus() calls (I don't think so) or if it is some part of the iframe code or even Stripe's. Is there a good way to monitor DOM events and do a stack trace for the trigger of each one?
I tried two tactics. Neither gave an obvious answer, but they did point my search in the right direction.
I opened the Event Listeners panel (in the Elements tab of my browser's developer tools) and removed everything I could find, but it seems that this doesn't actually change the behavior of the page- focus kept being stolen away. Luckily, I also noticed some listeners that were defined by the Material UI library.
I used monitorEvents() to get a few more details, but the src & target values were not much help and event.relatedTarget was always null.
In the end, I found this discussion and realized that my MUI Dialog component was stealing focus whenever I clicked on the iframe triggered by its content. This was easily fixed by adding the disableEnforceFocus attribute.
I'm trying to implement a solution to report detailed user activities to Google Analytics. Since the application is a single page application I know that I can capture the page change events from router, but this is only the basic part of the solution which is only for reporting seen pages.
Main point is since this is a large scale application containing 500+ pages/components and I don't want to create a logger service requires to change every page by adding it, I need to figure out a way to determine a centralized or application wide place to catch and detect button click events and maybe the pages they are fired from. Then I hope to be able to report detailed user activities on related pages to analytics like X button clicked from Y page.
For a central place I know there are interceptors for HTTP events, and router for page changes. Besides that I know maybe I can use change detection with hooks, but I'm not sure about should I have to work with hooking into application lifecycle level.
How can I implement a mechanism to catch and detect component elements events with related pages in Angular? Are there any best practices or abstractions that framework provides that may I utilize?
Note: I've learned that Google Tag Manager does the job specifically for the analytics purposes, but my question remain same which how to implement it with Angular.
Custom event API is one option, but you have to despatch that event in all clicks.
So using HostListner we can achieve this.
Add a listener in the main component ie in app.componnet
#HostListener('window:click', ['$event'])
onClickEvent(event: MouseEvent) {
var target = event.target || event.srcElement;
var id = target['id']; //this will be the id of the target that you clicked
this.clickedValue = id;
}
And follow a naming pattern while you adding the id of each item. ie add the component name as the prefix of id. After prod build, the component name will be minified to one-letter name, it loses its original name.
For eg: if you have component mychild.component
<div>
<h1 id="mychild-h1">Test</h1>
</div>
Here, id is myChild-h1 and you can pick the components name from it. Now you have the id and name of the component that you clicked and create a service to log that info.
Attaching one sample code for check
CheckThis
I created a window using var openedWindow = window.open(...).
I have a function which should listen to click event for a button in the new window, but it never fires.
Whenever I click btSearch button it should open another window and listen for #submitButton click event.
btSearch.Attributes["onclick"] = $#"
var openedWindow = window.open(myUrl, "", "toolbar=no,menubar=no,personalbar=no,width=650,height=399,scrollbars=yes,resizable=yes");
window.onModalExit(openedWindow);";
Function is aspx file
function onModalExit(modalWindow) {
$('#submitButton', modalWindow.document).ready(function () {
modalWindow.document.getElementById('submitButton').addEventListener('click', function () {
console.log('RETURNED CORRECTLY');
alert('Now');
});
});
}
you can send data from opened window to opener window via window.opener.postMessage() function.
Every window in a broswer is "isolated", that is there is no way for you to programaticly effect an other window.
That can be done only by the browser who is managing all those tabs. Or maybe by an installed extension for a browser, i suspect those have the power to inject stuff in the running pages probably
EDIT:
Now since that can not be done directly in any way that i know of, you could maybe:
Create a polling mechanism on the tab you want to read the value from
Store the value in some kind of server ( database, in-memory of a back-end etc )
Find a way to associate some kind of ids so you know what to look for.
So you will be transfering the value by taking a long way around PageA -> BackEnd -> PageB.
I am pretty condifent in saying that you can not effect/read from an other tab just by a plain page. You need something of higher layer, like the browser itself, or an extension that has the ability to inject/inspect pages, after a user has agreed to give permissions by installing the extension.
The only solution I see to solve your requirement, would be to use Sockets
But for this, you obviously need some kind of backend.
My app has a main view with some hidden elements that can be activated by the user. Like a typical sidebar on mobiles that slides in from the left. Or a cart that reveals the details when you tap on it.
The information in those initially hidden elements is always up to date since their content is mapped in the main app template. So my routes would not need to render any DOM.
The transitions are JS based. But now I want those states to be reflected in the URL, in order to get a consistent back button behavior.
How can I achieve that using the ember framework?
Update to make it more clear, what I am talking about:
To my understanding triggering routes in ember has mainly two side-effects:
Represent the new state in the URL, enabling consistent browser history support.
Render templates that manipulate the DOM based on some data
In my case, when for instance a user taps on the minimized cart I need:
Represent the new state in the URL, enabling consistent browser history support.
Execute my showCart() JS Function (no DOM changes, no template rendering)
When the user now taps on the browser back button, closeCart() should be executed (based on the fact that the state in the URL carries the information that the cart is open).
The problem is, where can they access these slide in windows? Can they only be accessed from a single location in your route map? Or can the user click on it regardless of whatever view they are in? And if they click it from different views, do you move them back to a different route just to pop out some slide in window?
If they can only click it from one place, then you could just add code in your setupController of that specific route to fire the js to slide out the window.
setupController: function(){
Ember.run.scheduleOnce('afterRender', this, function(){
//run some js to slide out the window
});
}
Honestly if they can click a button anywhere and have the slide out appear, I wouldn't try putting it into the url. Just my opinion though.
You can use the activate and deactivate methods from your show route, to know when is entered or exited from your route:
App.ShowRoute = Ember.Route.extend({
activate: function() {
alert('Entering in show');
// here would be your showCart
},
deactivate: function() {
alert('Exiting from show');
// and here the closeCart
}
});
I created a live demo here, to you see this working.
Application that I'm working on has multiple modules. Two are of a concern - main module and module that I write. And I have to call a function on window that contains main module and the problem is that I have to call that function not from page that is opened by parent webmodule, but from page to which user navigates from this page.
Basically first page presents just some query forms, lets user to make some query, and second holds query results, and I am supposed to update contents of parent page based on these results.
Navigation goes like that
Main module
First page of my module (i have main module page as an window.opener variable.
Second page of my module (and I would like to be able to open this page in the same browser window that the first one is opened)
And I would like to have as free navigation as possible - like opening query results in new tab, going back changing query parameters, making new query, etc. I would also like to present user query forms on page that displays results and let them to refine this query, and still be able to update main module.
I was thinking of following solutions:
Using AJAX to load query results to first window, but I would like to have this app as simple as possible, and AJAX is not simple ;)
Spawning new window on every request and doing code like var mainModule = opener.mainModule. Which is evil.
Embedding query results in a frame or iframe, but I havn'e got the slightest idea on how to inject main module window javascript variable into frame or iframe.
If being able to navigate in tabs is a requirement, I think you'd have to nix the idea of using a JavaScript opened window system. Because the opener property is definitely lost in Firefox, Safari, and most browsers, when you navigate to a different window. A new tab disturbs the neat sandbox.
Not being a requirement, per your comment, I think you can use either of 3 methods:
Parent-Child window communication-- which I will take up
next;
XMLHTTP Requests (a.k.a.
AJAX); or
iFrames (the old way
to remote to the server :)
I'll take the Parent child communication angle, here, since you seem to be most comfortable with it.
Inter-navigation in a "Child" window is easy.
any link on the page loads in the child and shares the same "opener".
the parent can reload a different page and it shares the same opener.
There will be a parent-listener function in the child;
The child will have a separate function to talk to the parent.
The parent will have one or more child listeners, depending on how
generic, or specific your needs.
I've updated (not completely) an article I wrote years ago, to let you play around with the windows and to actually do a minimal form submission. The communication alerts are rather verbose; but you will have no doubt as to who is communicating what to whom. The first child is annoyingly opened onload. But there is a link on the page to change the child to a server-generated form.
JavaScript: Beyond Parent Child Windows
EXAMPLE CODE SNIPPETS:
A Link:
open a window from link
Link Listener:
The event listener and target property are set up in the head of the document, in JavaScript that executes onload:
var mywin; //global variable for best results
//XDOM - normalizes browser differences:
var openingLink = XDOM.getElementById('newwinlink');
openingLink.target = "newWin"; //important!
XDOM.addListener(openingLink, 'click', function(e){mywin=window.open('','newWin','width=400,height=400,resizable,scrollbars');if (!mywin.opener){mywin.opener = self;}return true}, false);
Child Document - Parent Listener:
function parentListener(pmsg)
{
alert("I'm the child, and I just received the following message from my parent:\n\n" + pmsg);
}
Child Document - Talk to Parent:
function talktoParent()
{
if (self.opener!=null) {
opener.childListener("Hi from child window!");
} else {
alert("no opener... sorry, can't talk now");
}
}
Parent Document - Child Listener:
function childListener(cmsg)
{
alert("I'm the parent. Just received the following message from my child:\n\n" + cmsg);
//send back a message to the child, mywin...
mywin.parentListener("Hi, back, from parent window!");
}
These are simplistic. But you can see opener continuity, navigation, and communication between server-side postbacks and the Parent at the link provided above.
Again the downside is that opening any of these in another tab will lose the connection to the parent. Try it over on the page that I sent you to. I believe the child is set to alert you that it is disconnected from its "opener".
Feel free to ask questions, jb.
iirc, even after you navigate away from the original document in a window opened by window.open, window.opener is still available.