I have an action that I want to allow only to an authenticated user. If a user who hasn't yet logged in clicks this link, the Webserver will return a not-logged-in response. I then present the login form in a floating "lightbox" div. If the user successfully logs in, I want the browser to retry the action.
I'm having trouble finding a good place to store the function that I want to retry. Currently, I'm attaching it to a global object, which seems very hacky. The jQuery "deferred" pattern seems to fit here, but I would still need to retain a reference to the deferred object somewhere, and I can't see the right place to do it.
Suggestions?
Here's some simplified code (in reality, the Ajax response determines whether we show the login form or a confirmation of the action):
$(function() {
$('li.secured').find('a').click(function() {
var link = this;
var action = function() { // captures 'link'
if(RootObj.IsAuthenticated() ) { // already logged in
window.location.assign( link.href );
} else {
// pass ourself in as a callback to be repeated after authentication
// Yes, this seems to work even in the midst of our own definition...
RootObj.ShowLoginForm( action );
}
return false; // don't go immediately to the new url
}
return action();
});
});
RootObj.ShowLoginForm = function( callback ) {
var lb = $('#lightbox');
if(lb.length == 0) { lb = $('<div id="lightbox" class="lightbox"></div>').appendTo('body'); }
lb.load('login.html');
RootObj.callback = callback; // is there a better place to store this callback function?
}
Related
I have a simple signup form on my site where users enter their email and name.
To avoid having a double signup error, I have a simple Ajax call to see if the email is already there. If so, I do nothing, if not, I submit the form.
My problem is that the form has a target of _blank, so the delayed submit call triggers the popup blocker on browsers.
Now, I know why this is happening - I'm no longer in the trusted click event. I changed the Ajax call to be synchronous, but I'm getting a warning that sync Ajax calls on the main thread are deprecated.
So my current options seem to be:
Use sync Ajax call anyway, but eventually it'll stop working
Remove the target _blank on my form, so it redirects on the same page (bad UX)
???
Is there a better way to get around the popup blockers seeing my delayed form submit as a popup?
Code:
HTML:
<form action="//external-site.com" method="post" target="_blank" onsubmit="onSubmitSignupForm(event)">
...
</form>
JS:
function onSubmitSignupForm( e )
{
e.preventDefault();
var request = new XMLHttpRequest();
request.open( 'POST', 'checkEmail.php', true );
request.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8' );
request.onload = onLoad;
request.onerror = onError;
// NOTE: params = urlencoded params recovered from form
request.send( params );
}
function onLoad( e )
{
var request = e.target;
if( request.status == 200 )
{
var data = JSON.parse( request.responseText );
if( data != null && data.success )
{
onFinish( request, data );
return;
}
}
// if we get here, then it means that the request failed, or there was a problem
onError( e );
}
function onError( e )
{
var request = e.target;
var data = JSON.parse( request.responseText );
onFinish( request, data );
}
function onFinish( request, data )
{
var success = ( data != null ) ? data.success : false;
if( !success )
$form.submit(); // blocked by popup
}
OK, so I have a solution, though it may be a little specific to my situation and is helped by the fact that I redirect the user if they're already signed up.
I basically open a new, intermediary page when the form is initially submitted (thus getting around the popup issue as it's in a trusted event) and populate a hidden form on that page (with a link to manually submit it if the user wants). The ajax call is sent, and depending on the results, I either submit the form on page 2, or redirect if they're already signed up.
User submits form on Page1
Stop event, and open Page2, which has the same form, albeit hidden
Populate the form on Page2 with the values from the form on Page1
Show a message on Page2 saying we're working on it, along with a link to manually submit the hidden form (good UX)
On Page1, fire Ajax request to see if the user is already subscribed
When Ajax results come in, 2 things can happen
(a) The user isn't signed up - form on Page2 is submitted (can optionally redirect Page1 to the content)
(b) The user is already signed up - redirect Page2 to the content
The only things to watch out for is to make sure that Page2 is fully loaded before filling the form and the Ajax results come back. For that, I use a simple boolean system with an onload event.
The code for that looks something like this:
function onSubmitSignupForm( e )
{
e.preventDefault();
var $form = e.target;
var name = ...; // get name value from form
var email = ...; // get email value from form
// open a new window that we'll be able to control and redirect in (otherwise, we'll
// get stung with a trying to open a popup, as when we get the result, we'll no longer
// be in the trusted click event)
var win = window.open( '/signup/', '_blank' );
win.onload = function() {
win.setDetails( name, email ); // fill in the hidden form
}
// check if the email is already signed up
// NOTE: result might be null if we couldn't reach the server
checkEmail( name, email, function onFinish( result ){
// set the result in the other window
// NOTE: the window might not have loaded yet
if( typeof win.setResult == 'undefined' )
{
var f = win.onload; // replace the one we already have
win.onload = function(){
if( f )
f();
win.setResult( result );
}
}
else
win.setResult( result );
});
}
Both setDetails and setResult will try to call a third function handleResult that will only continue booleans set by both functions are true.
$("#login").click(function(){
$.getJSON("handlers/Login.php?name="+$("#username").val(), function(data){
console.log(data); //get whatever i have in login box and sends to my handler returning the json array
});
template = $("#hidebody");
if(!data['error']){
template.show();
//$("#greeting")
}
else
{
template.hide();
}
return false;
});
So far the top part works, which means whatever name i type in username box it is send to my handler login.php, when i do a console.log, and so on console i get my json array back from my database which works, now I need some help writing a if and else statment where the comment box appears after the person logs in, which hides the userbox, and password box, and as a greet statment displaying the person name from the database.
Since Javascript is an asynchronous language, there's no guarantee that the data from $.getJSON will be available unless passed through a callback or returned in a promise. In my below example, I pulled the function definitions out into their own variables instead of defining them inline, to help illustrate the flow of the program.
// Define a login method which grabs data and passes it to a callback
var login = function (username, callback) {
$.getJSON("handlers/Login.php?name=" + username, callback);
};
// Define a method which handles the response returned from the login URL
var handleResponse = function (data) {
console.log(data);
var template = $('#hidebody');
if(!data['error']) {
template.show();
} else {
template.hide();
}
};
// Register the onClick event which calls the login method
$("#login").click(login($('#username').val(), handleResponse));
Since getJSON is going to run asynchronously you need to add your data handling functionality into that callback where you are currently logging the data.
Something like this:
$("#login").click(function(){
$.getJSON("handlers/Login.php?name="+$("#username").val(), function(data){
console.log(data); //get whatever i have in login box and sends to my handler returning the json array
template = $("#hidebody");
// Any other logic for parsing / handling of returned JSON
if(!data['error']){
template.show();
//$("#greeting")
}
else
{
template.hide();
}
});
return false;
});
We have a requirement to refresh the form after saving (to ensure that some hide/show logic works as expected based on a field value).
Currently the form does not automatically refresh after saving the record.
I have gone through some articles and found this reference:
http://msdn.microsoft.com/en-us/library/dn481607%28v=crm.6%29.aspx
When I try to do either of the below, it results in an infinite loop and throws a 'Callstack exceeded Max limit' error.
OnSave(context)
{
//my logic
...
...
Xrm.Page.data.save.then(SuccessOnSave, ErrorOnSave);
}
function SuccessOnSave()
{
//force refresh
Xrm.Page.data.refresh();
}
function ErrorOnSave()
{
//do nothing
}
OnSave(context)
{
...
...
//force refresh
Xrm.Page.data.refresh(true).then(SuccessOnSave, ErrorOnSave);
}
function SuccessOnSave()
{
//do nothing
}
function ErrorOnSave()
{
//do nothing
}
Can someone please explain me how to use the refresh or save method to do a force refresh of the form ??
Rajesh
I use to achieve it with following code
Xrm.Page.data.save().then(
function () {
Xrm.Page.data.entity.attributes.forEach(function (attribute, index) {
attribute.setSubmitMode("never");
});
Xrm.Utility.openEntityForm(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId());
},
function (errorCode, message) {
}
);
For me this is what solved the purpose (CRM 2015)
// Save the current record to prevent messages about unsaved changes
Xrm.Page.data.entity.save();
setTimeout(function () {
// Call the Open Entity Form method and pass through the current entity name and ID to force CRM to reload the record
Xrm.Utility.openEntityForm(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId());
}, 3000);
If you want to do a hard refresh on the form data, you will likely want to do a location reload. What I've done in the past is put the refresh logic in a function that is called when the Form is loaded (after being saved). The tricky part about this is that the function can get called if the form is auto-saved in CRM 2013. You also want to take into account that you don't want to refresh the form on the first load, since this would result in an infinite reloading loop. Here's an example:
var formLoaded = false;
function formLoad() {
if (formLoaded) {
window.location = location.href;
}
formLoaded = true;
}
You have attached the OnSave() method to OnSave event. So, logically if you call save again within the same event, the calls goes recursively.
From MSDN
Xrm.Page.data.refresh(save).then(successCallback, errorCallback);
Parameter: save - A Boolean value to indicate if data should be saved
after it is refreshed.
So, you will have to pass 'false' to this method (You just need to refresh, no save is required)
As I couldn't find the complete code for this written in a 'generically' reusable way, here goes:
var triggeredSave = false;
//Attach the OnSave Form event to this OnSave function
//and select passing of context as the first parameter.
//Could instead be attached programmatically using the code:
//Xrm.Page.data.entity.addOnSave(OnSave);
function OnSave(context) {
var eventArgs = context.getEventArgs();
var preventedAutoSave = false;
//Preventing auto-save is optional; remove or comment this line if not required.
preventedAutoSave = PreventAutoSave(eventArgs);
//In order to setup an after save event function, explicitly
//invoke the save method with callback options.
//As this is already executing within the OnSave event, use Boolean,
//triggeredSave, to prevent an infinite save loop.
if (!preventedAutoSave && !triggeredSave) {
triggeredSave = true;
Xrm.Page.data.save().then(
function () {
//As the form does not automatically reload after a save,
//set the save controlling Boolean, triggeredSave, back to
//false to allow 'callback hookup' in any subsequent save.
triggeredSave = false;
OnSuccessfulSave();
},
function (errorCode, message) {
triggeredSave = false;
//OPTIONAL TODO: Response to failed save.
});
}
}
function PreventAutoSave(eventArgs) {
if (eventArgs.getSaveMode() == 70 || eventArgs.getSaveMode() == 2) {
eventArgs.preventDefault();
return true;
}
return false;
}
//Function OnSuccessfulSave is invoked AFTER a save has been committed.
function OnSuccessfulSave() {
//It seems CRM doesn't always clear the IsFormDirty state
//by the point callback is executed, so do it explicitly.
Xrm.Page.data.setFormDirty(false);
//TODO: WHATEVER POST SAVE PROCESSING YOU REQUIRE
//e.g. reload the form as per pre CRM 2013 behaviour.
ReloadForm(false);
//One scenario this post save event is useful for is retriggering
//Business Rules utilising a field which is not submitted during save.
//For example, if you implement a Current User field populated on Form
//Load, this can be used for user comparison Business Rules but you
//may not wish to persist this field and hence you may set its submit
//mode to 'never'.
//CRM's internal retriggering of Business Rules post save doesn't
//consider changes in fields not submitted so rules utilising them may
//not be automatically re-evaluated or may be re-evaluated incorrectly.
}
function ReloadForm(preventSavePrompt) {
if (preventSavePrompt) {
Xrm.Page.data.entity.attributes.forEach(function (attribute, index) {
attribute.setSubmitMode("never");
});
Xrm.Page.data.setFormDirty(false);
}
Xrm.Utility.openEntityForm(Xrm.Page.data.entity.getEntityName(),
Xrm.Page.data.entity.getId());
//Another way of trying Form reload is:
//window.location.reload(true);
}
Use Mscrm.Utilities.reloadPage();
I found this post helpful in demonstrating the difference between Xrm.Page.data.refresh() and Xrm.Utility.openEntityForm(entityName, id).
TL;DR - if you want the screen to repaint, consider using Xrm.Utility.openEntityForm(entityName, id).
You can achieve by placing Modified On field on the form. And set visible by default property to false.
Use below JS to refresh the form
function refreshCRMFORM()
{
setTimeout(function () {
// Call the Open Entity Form method and pass through the current entity name and ID to force CRM to reload the record
Xrm.Utility.openEntityForm(Xrm.Page.data.entity.getEntityName(), Xrm.Page.data.entity.getId());
}, 1000);
}
Create On Change event on Modified on field and provide above function name.
When the city input field is blurred I get somnething via an ajax request and set that as the value of a hidden field in the same form that the city field resides in.
$('input#city').on('blur', function() {
$.ajax({
url: 'get/something?param=val',
success: function(response) {
$('input:hidden[name="something"]').val(response);
}
});
});
If the user submits the form immediately after blurring off the city field sometimes due to latency the hidden field is not populated because the SQL on the other end is taking too long.
The form that both these fields are in is also submitted via ajax:
$('form#find-users').on('submit', function() {
if(NO_AJAX_CURRENTLY_RUNNING_ON_PAGE) {
// do stuff
}
});
How to detect if no ajax is running on the page? This will ensure that the city ajax was completed and the hidden field populated before the form is processed.
EDIT
Actually it won't, it will only prevent the form from being submitted. But if I can detect that then I can use a setInterval and keep trying to run that code until it runs because ajax is complete. Ideally there will be something in jQuery that waits until other ajax is complete and then submits.
Use jQuery's Ajax Events. As long as all of your Ajax calls are generated using jQuery, you have a way of knowing if any Ajax calls are outstanding.
$(document).ready(function() {
var ajaxBusy = false;
$(document).ajaxStart( function() {
ajaxBusy = true;
}).ajaxStop( function() {
ajaxBusy = false;
});
});
Edit:
So that answers your direct question about "How do I know if there is any Ajax call running."
Alternatively, you could disable the form's submit buttons when run your blur handler, and then re-enable it when you're done.
$('input#city').on('blur', function() {
var submit = $(this).closest('form').find(':submit:enabled');
submit.prop('disabled', true);
$.ajax('get/something?param=val').done(function(response) {
$('input:hidden[name="something"]').val(response);
}).always(function() {
submit.prop('disabled', false);
});
});
Edit 2:
So now we're at the point where we would like to delay the form submission until all current Ajax calls have completed. We let people click on the submit button, but if there are pending Ajax calls we don't do anything right away.
We can use a Deferred object to help us with this.
$(document).ready(function() {
var ajaxDefer = $.Deferred().resolve();
$(document).ajaxStart( function() {
ajaxDefer = $.Deferred();
}).ajaxStop( function() {
ajaxDefer.resolve();
});
$('form#find-users').on('submit', function() {
ajaxDefer.always(function() {
// Code here will always be executed as soon as there are no
// Ajax calls running.
// this points to the deferred object (ajaxDefer), so use the closure
// to carry over any variables you need.
});
});
});
When we're just starting out, we set up our ajaxDefer object in a resolved state. That means any functions attached using .always() will execute immediately.
When the first Ajax call starts, we replace the old ajaxDefer object with a new one that has not been resolved. Any new functions attached using ajaxDefer.always() will be deferred until later.
When the last Ajax call completes, we call ajaxDefer.resolve(), which causes any unexecuted deferred functions to execute. Now we're back to our initial state, where any newly-attached functions will execute immediately.
When somebody tries to submit the form, create an anonymous function that does the work and attach it to ajaxDefer. It will get executed when appropriate, depending on if there are any outstanding Ajax requests or not. Be mindful of your closures.
Use this to check if AJAX calls are currently in-progress using JQuery:
if ($.active == 0) {
...
}
you can put a variable in the global namespace, perhaps named ajaxLock and toggle it on when AJAX starts and off when the response comes. Then check it before allowing submit.
something like
var ajaxLock = 1;
$('input#city').on('blur', function() {
$.ajax({
url: 'get/something?param=val',
success: function(response) {
$('input:hidden[name="something"]').val(response);
ajaxLock = 0;
}
});
});
Use a lock variable like you suggested:
$('input#city').on('blur', function() {
window.AJAX_CURRENTLY_RUNNING_ON_PAGE = true;
$.ajax({
url: 'get/something?param=val',
success: function(response) {
$('input:hidden[name="something"]').val(response);
},
complete: function() { window.AJAX_CURRENTLY_RUNNING_ON_PAGE = false; }
});
});
$('form#find-users').on('submit', function() {
if(window.AJAX_CURRENTLY_RUNNING_ON_PAGE) {
return;
}
//dostuff
});
What i could have done on this circumstances is to use plugin like block ui or disable the form submit button,the reason is you need to be interactive in your design,you may well able to lock the form submission,but its better to give a message or have a modal gray out
I have looked in lots of places for this, and I'm probably being an idiot because this is done a lot, but here's my situation.
I am trying to display a checkbox next to an e-mail field on my website iff the e-mail has not been used to register already.
What I have is something like this:
$('[name=reg_email]').change( function() {
if(!emailUsed()) {
//Update image to be a green checkmark
} else {
//Update image to be a huge red X
}
}
And my "emailUsed" function should be returning a Javascript boolean variable depending on whether or not the e-mail address is in the database. To facilitate this, I've created a script which will determine if it's there or not. So the emailUsed() function just needs to call the script and return, but I need to wait until readystate == 4 before I return, and all of the methods I have found for waiting for readystate to equal 4 prevent me from returning a value at all:
function emailUsed() {
var req = $.get('scripts/email_used.php?email='+$('[name=reg_email]').val());
//Wait for req.readystate to be 4
return req.responseText == 'True';
}
But nowhere can I find something that explains how to do this and still return a value. Everything seems to use callback functions and this and that, but I can't get THIS method to return a value.
Help, please!
Doing a busy wait for req.readyState === 4 is considered bad design practice. You're tying up the UI thread and preventing the browser from updating. If the server is slow enough to respond, the user will get prompted whether to cancel further processing.
If you take a look at $.get(), it takes a completion function as one of its arguments. You should perform your success/failure logic in this function. You can do this by disabling your Submit button until you get back a success message.
You're missing the point of asynchronous methods. The main point is that there is some calculation to be done, and you don't want to tie up the current thread waiting for that calculation. Therefore, long running methods should not return a value, but instead they should provide a callback that will be passed the status of the call, without making the entire app wait.
I would suggest the following
function emailUsed (callback) {
var req = $.get('scripts/email_used.php?email='+$('[name=reg_email]').val(),
function(data) {
callback(data == 'True');
}
);
}
$('[name=reg_email]').change( function() {
emailUsed(function(isUsed){
if (isUsed) {
//Update image to be a green checkmark
} else {
//Update image to be a huge red X
}
});
}