How to handle Unsaved Changes in LWC component? - javascript

I am trying to implement unsaved changes functionality in the LWC component. Tried various approaches including using javascript & jquery and tried to fire it onbeforeunload but things don't seem to work. The same works on the HTML page but not as expected in LWC.
I tried using the below code in a util class:
const handleUnsavedChangesValidation = (hasModifiedData, updateClicked) => {
console.log('>>>:::INN::hasModifiedData::', hasModifiedData);
console.log('>>>:::INN::updateClicked::', updateClicked);
var modified = hasModifiedData && !updateClicked ? true : false;
if(modified) {
addEventListener("beforeunload", (evt) => {
const unsaved_changes_warning = "Changes you made may not be saved.";
evt.returnValue = unsaved_changes_warning;
return unsaved_changes_warning;
});
}
else
{
removeEventListener("beforeunload", () => {
return true;
});
}
}
On any changes to the input field I tried calling the following method with these params :
handleUnsavedChangesValidation(true, false)
On save button click I tried calling this
handleUnsavedChangesValidation(true, true);
On trying the above, it works partially fine:
On load -> alert is not displayed (as expected)
On input updates -> If refresh is pressed (alert displays - expected)
On input updates -> If the tab is clicked is pressed (alert displays - expected)
On input updates -> If browser back button is clicked (it fails to display alert)
On input updates -> If update button is clicked (it still displays alert stating unsaved changes and then on click of Ok it saves. This is also not expected. Alert should not display here)
Post save -> On page refresh -> Now alert displays which is also not expected.
Once again if I click refresh it does not display an alert.
I am looped and tried finding many ways to handle this via static resource and other ways but could not do anything here. Tried to pass blank to the return value of unload yet no result. Tried passing return true as well as return false. Yet it seems to display an alert when we click on save.
Anyone knows any alternative or can someone please help me out here. I am looking for a solution that works for LWC implementation and not just html javascript.
Please help geeks. Any help is highly appreciated.
Thanks in advance, stay safe.
Regards,
Rajesh

Related

Tab order on prompt page

Using Cognos Analtyics 11.1.7IF9.
I have a user who, oddly enough, wants Cognos to make his workflow more efficient. (The nerve!) He thinks that if he can use the TAB button to navigate a prompt page, he'll be faster because he never needs to reach for the mouse.
To test this I created a simple report with a very simple prompt page using only textbox prompts. As I tab I notice it tabs to everything in the browser: browser tabs, the address bar, other objects in Cognos, ...even the labels (text items) I created for the prompts. Oh... and yes, at some point focus lands on a prompt control.
Within Cognos, I see that the tab order generally appears to be from the top down. (I haven't tried multiple columns of prompts in a table yet.) I must tab through the visual elements between the prompts. Also, while value prompts get focus, there is no visible indication of this.
Is there a way to set the tab order for the prompts on a prompt page?
Can I force it to skip the non-prompt elements?
Can the prompts be made to indicate that they have focus?
I tagged this question with javascript because I figure the answer will likely involve a Custom Control or a Page Module.
Of course, then I'll need to figure out how all this will work with cascading prompts and conditional blocks.
I found a similar post complaining about this being a problem in Cognos 8. The answer contains no detail. It just says to go to a non-existent web page.
I had the same frustration as your user and I made a solution a while back that could work for you. It's not the most elegant javascript and I get a weird error in the console but functionally it works so I haven't needed to fix it.
I created a custom control script that does 2 things on a prompt page.
First, it removes the ability to "select" text item elements on the page. If you only have text items and prompts on the page it sets it's "Tabindex" to "-1". This allows you to tab from one prompt field to the next without it selecting invisible elements or text elements between prompts.
Secondly, if you press "Enter" on the keyboard it automatically submits the form. I am pasting the code below which you can save as a .js and call it in a custom control on a prompt page. Set the UI Type to "None"
define( function() {
"use strict";
function AdvancedControl()
{
};
AdvancedControl.prototype.initialize = function( oControlHost, fnDoneInitializing )
{
function enterSubmit (e)
{
if(e.keyCode === 13)
{
try {oControlHost.finish();} catch {}
}
};
function setTab () {
let nL = [...document.querySelectorAll("[specname=textItem]")]
//console.log(nL)
nL.forEach((node) =>{
node.setAttribute('tabindex','-1')
})
};
setTab();
let exec_submit = document.addEventListener("keydown", enterSubmit, false);
try {exec_submit;} catch {}
fnDoneInitializing();
};
return AdvancedControl;
});

Dynamics CRM 2016 Online - Refresh record after using dialog

I have a custom button on my ribbon which fires a dialog up. It's part of a workaround Qualification solution I'm putting together.
The creation of an Account/Contact/Opportunity and the choices given work fine, as well as changing the status of the Lead to qualified. The problem is that when the user is done with the Dialog and closes it, they're still looking at the Lead in its original state.
How do I force the form to refresh so that it shows its new state?
I've seen a Javascript solution online (codeplex), Process.js - callDialog() which seems popular but it doesn't want to work as described by the creator on my version of CRM - always get a invalid URL error message & it fires on load of the form as well as when using the custom button.
Has anyone come across a requirement like this and how have you resolved it?
Thanks
Edit: Here is the JS I use on my ribbon button currently. Where do I put my refresh call and what/how do I call the event being used when closing the Dialog.
I tried adding a refresh call at the bottom of the this code but its called whilst opening the Dialog at the start, which isn't much use as the changes I want to see are applied throughout the Dialog itself.
Thanks
Develop1_RibbonCommands_runDialogForm = function(objectTypeCode, dialogId) {
var primaryEntityId = Xrm.Page.data.entity.getId();
var rundialog = Mscrm.CrmUri.create('/cs/dialog/rundialog.aspx');
rundialog.get_query()['DialogId'] = dialogId;
rundialog.get_query()['ObjectId'] = primaryEntityId;
rundialog.get_query()['EntityName'] = objectTypeCode;
var hostWindow = window;
if (typeof(openStdWin) == 'undefined') {
hostWindow = window.parent; // Support for Turbo-forms in CRM2015 Update 1
}
if (typeof(hostWindow.openStdWin) != 'undefined') {
hostWindow.openStdDlgWithCallback(rundialog, hostWindow.buildWinName(null), 615, 480, Xrm.Page.data.refresh(false));
}
}
})();
Check out the Xrm.Page.data (client-side reference), you'll want to call Xrm.Page.data.refresh().
Depending on how you're launching your dialog, and assuming your dialog is a webresource hosted in CRM, the dialog can reach back out to the form it launched from and call refresh, or a callback could potentially be used.
EDIT (based on your posted code): If the 5th parameter of the function openStdDlgWithCallback is the callback for when the dialog closes you'd want to pass the function like Xrm.Page.data.refresh or wrap your call in a function function(){Xrm.Page.data.refresh()}. Currently your code is executing the function right away which is why you're seeing the refresh right away.
Using openStdDlgWithCallback you can subscribe a callback function which runs after the dialog is closed. You can then use Xrm.Page.data.refresh() inside the callback function.

Programmatically change first page jQuery Mobile shows

My goal is to show a different first page depending on whether the user is logged in or not. The login check happens using a synchronous Ajax call, the outcome of which decides whether to show a login dialog or the first user page.
The normal way to do this would be to set $.mobile.autoInitialize = false and then later on initialize programmatically, as described in the answer to this question. For some reason this won't work, instead another page gets loaded every single time.
I decided to give up on this way and try out a different parcour. I now use a placeholder, empty startup page that should be shown for as long as the login check takes. After the login check it should automatically change. This is done by calling a function that performs the ajax call needed for authentication on the pagechange event that introduces this startup page. The function takes care of changing to the outcome page as well.
The trick is that it doesn't quite do that.. Instead it shows the correct page for just a short time, then changes back to the placeholder. Calling preventDefault in pagechange didn't prevent this, as described in the tutorial on dynamic pages. Adding a timer fixed this, leading me to think that the placeholder wasn't quite finished when pageshow got fired (as per this page on page events), or some side-effect of the initial page load still lingered.
I'm really clueless as to how to fix this seemingly trivial, yet burdensome problem. What causes this extra change back to the initial page? Also, if my approach to intercepting the initial page load is wrong, what would be the correct approach instead?
I use jQuery Mobile 1.4.0 and jQuery 1.10.2 (1.8.3 before).
EDIT: Below is the code to my last try before I posted the question here. It does not work: preventDefault does not prevent the transition to the placeholder page.
$(document).on("pagebeforechange", function(e, data) {
if (typeof(data.options.fromPage) === "undefined" && data.toPage[0].id === "startup") {
e.preventDefault();
initLogin();
}
});
function initLogin() {
// ... Login logic
if (!loggedIn) // Pseudo
$('body').pagecontainer("change", "#login", {});
}
If you're using a Multi-page model, you can prevent showing any page on pagebeforechange event. This event fires twice for each page, once for the page which is about to be hidden and once for the page which is about to be shown; however, no changes is commenced in this stage.
It collects data from both pages and pass them to change page function. The collected data is represented as a second argument object, that can be retrieved once the event is triggered.
What you need from this object is two properties, .toPage and .options.fromPage. Based on these properties values, you decide whether to show first page or immediately move to another one.
var logged = false; /* for demo */
$(document).on("pagebeforechange", function (e, data) {
if (!logged && data.toPage[0].id == "homePage" && typeof data.options.fromPage == "undefined") {
/* immediately show login dialig */
$.mobile.pageContainer.pagecontainer("change", "#loginDialog", {
transition: "flip"
});
e.preventDefault(); /* this will stop showing first page */
}
});
data.toPage[0].id value is first page in DOM id.
data.options.fromPage should be undefined as it shouldn't be redirected from another page within the same webapp.
Demo
I'm undergoing the same problem as the one described by #RubenVereecken, that is, a coming back to the initial page once the cange to my second page has completed. In fact, he posed the question "What causes this extra change back to the initial page?" and it hasn't been replied yet.
Unfortunately, I don't know the reason since I haven't found how the page-event order works in JQM-1.4.2 yet, but fortunately, the workaround suggested by #Omar is working for me.
It's not exactly the same code but the general idea works at the time of preventing a coming back to the initial page. My code is as follows:
$(document).on("pagebeforechange", function(event, data) {
if ( typeof (data.toPage) == "string") {
if (data.toPage.indexOf("#") == -1 && typeof (data.options.fromPage[0].id) == string") {
event.preventDefault();
}
}});
The condition data.toPage.indexOf("#") == -1 is because I checked that all the undesired coming-backs to the initial page were happening when the property '.toPage' was set to something like [http://localhost/.../index.html].

javascript history.back losses the search result

Page A:
$(document).ready(function () {
bindData();
});
function bindData() {
$('#searchbtn').bind('click', function () { SearchResult(); });
}
function SearchResult() {
ajax call...
}
Page A HTML:
<input type="button" id="searchbtn" />
Page B Details---> this page comes after selecting a specific search result from page A search list
Back<br />
Now when I go back to the Page A I can see my search criteria's as they were selected but the result Div is gone. What I am trying to do is I want the search list to stay when the Page comes back.
I think what I can do here is some how call the searchbtn click event again when the page comes back so the list will come-up again. Can anyone tell me how to fire the searchbtn click event only when the page comes back from Page B. or point me in the right way of doing this..
Thanks
The Browser Back button has long been problematic with AJAX. There are scripts, workarounds, and techniques out there (depending on the framework that you want to use).
Since it appears that you are using jQuery (based on your posted JavaScript syntax), here is a link to another Stackoverflow post regarding back button jQuery plugins.
history.back() will return you to the last URL visited, meaning that any ajax calls made during the user's visit will not be automatically repeated. Your browser may automatically restore your form selections, but the SearchResults() function is only called by a click event, not a selection event.
You can bind URLs to ajax states using a framework like sammy.js. That way, history.back() would take you to a URL associated with SearchResults().
function bindData() {
var chkinput1 = $("input:checkbox[name=x]:checked").length;
var chkinput2 = $("input:checkbox[name=y]:checked").length;
if (chkinput1 > 0 && chkinput2 > 0) {
SearchResult();
}
$('#searchbtn').bind('click', function () { SearchResult(); });
}
I know this is the worst way to achieve this result but I think instead of using any other plugins to add complexity we will go with this for now. If anyone else is looking for the same question let me tell you again this is not the best practice as on returning back to the history we are calling the search result again depending upon the cached input selection of checkboxes and generating the whole ajax call again to display the list. On the first request I am caching the list and setting sliding expiration so its not taking anytime to comeback and so everyone lives happily.

How to add history to jQuery UI tabs using a back button

I've got a PHP file with 5 tabs (jquery ui). Tab four and five contain
forms. Forms and tab work fine - expect to this: I submit the form (POST
method not XHR), then click the right mouse button (Firefox and IE behave
identical) and select back and then select tab five in the page by mouse
click the entered form data is still available.
I try to build a link, that is more convenient for the user.
<a href="#" onClick='history.back();$("#tabs").tabs("select","4");'>modify</a>
If click on my modify link, it still jumps back to tab one and the form fields in tab five are empty.
I read several posts about jQuery UI tabs and the back button, but all seem not to address my problem.
Where is my fault and is the difference between doing this steps by hand and my link with JS?
Javascript stops executing once you leave the page that it's running on -- the second half of your onClick handler never runs.
Following from the comments here is a function that will remember what your last tab was that you selected. It does rely on you using a set "Back" button.
The problem you will find, as far as I can see, is that you can't intercept a user clicking the browser back button. I have found that creating an obvious and clear back button on the site does the job and the feedback I have had so far on our sites seem to back that up.
The function is:
$(function() {
var $previousTab = 0;
var $backButtonUsed = false;
// Initialise tabs
$("#tabs").tabs();
$("#tabs").bind("tabsselect", function(event, ui) {
if ($backButtonUsed)
{
$backButtonUsed = false;
} else {
$previousTab = $("#tabs").tabs('option', 'selected');
}
return true;
});
$("#back").live('click', function() {
$backButtonUsed = true;
$("#tabs").tabs({ selected: $previousTab });
return true;
});
});​
I have also included this in a JSFiddle, so you can see it in action with the HTML and jQuery UI Tabs.
Let me know what you think.

Categories