Custom RN Ajax Success Handlers

The RightNow Customer Portal website solution heavily utilizes JavaScript and AJAX features as part of its core functionality. While these features are very powerful, some otherwise simple development tasks become exponentially more complicated. One category of customizations I've historically struggled with was performing custom logic after a form submit has occurred. I've run into this type of customization many times; some projects need to dynamically redirect to a different page than the default confirmation page; others want to send some custom tracking to Google Analytics. Historically, my solution was to customize the Form Submit widget and add my custom logic there.

I've found a better way of doing this that doesn't require the extension of the Form Submit widget. It is possible to subscribe to all AJAX events in RightNow. Your subscribed function has the ability to redefine the AJAX call's success handler. When done correctly, you can inject your own custom logic then call the original handler when appropriate. The possibilities are endless; using this technique you can do things like:

  • Completely redefine the success handler
  • Modify the AJAX response payload before calling the default success handler
  • Run your own logic before calling the original success handler

So how does this work? Let's walk through an example.

My demonstration use case is that after an "Ask" form submit has been processed by the server, custom logic determines where to redirect the user. If the user wants to submit another Incident, I want to reload the "Ask" page; otherwise I want to direct them to the success page (the default behavior).

I will ask my user if they want to submit another Incident using a JavaScript confirm dialog. If they select "OK" the ask page will reload. If they hit "Cancel", the standard success handler will be executed; this results in the user being taken to the page indicated by the FormSubmit widget's "on_success_url" attribute. My confirm dialog will display the Incident Reference Number as part of its user message.

Full Code

This code is designed to be placed into the autoload.js file, but this is not required. The autoload.js is an easy way to load JavaScript code on every single page. You could also save this file as a stand alone .js file and include it on your ask.php page or associated template. This is the full code which I'll explain in further detail.

/** * Custom RightNow Ajax Success Handler * * @author Andy Rowse <andy@45n.co> * @license * The MIT License (MIT) * Copyright (c) 2015 45 North Solutions LLC * */ (function(){ var path = window.location.pathname.toLowerCase();; /** * Handle Special tagging events on CONTACT US page */ if(RightNow.Text.beginsWith(path, "/app/ask")) { //Register Ajax Intercept RightNow.Event.subscribe("on_before_ajax_request", function(name, eo) { var requestObj = eo[0]; //get pointer //Only for Form Submits. Other ajax events will occur //(product load, state/prov load, email val). //We don't want to modify their behavior. if(requestObj.scope && requestObj.scope.data && requestObj.scope.data.info && requestObj.scope.data.info.name === "FormSubmit") { //Store default RN success handler on a prototype chain requestObj.prototype = {}; requestObj.prototype.successHandler = requestObj.successHandler; //Define our own success handler. //This handler will prompt the user with a confirm dialog, then call the //default RN success handler if appropriate requestObj.successHandler = function(response, argument, transactionID) { var self = this; var refno = response.result.refno; var result = confirm( "Your Incident was submitted. Reference Number: " + refno + "\nDo you want to submit another Incident?"); if(!result) { //Perform default action requestObj.prototype.successHandler.call(self, response, argument, transactionID); } else { //Reload the page so that the user can submit another event window.location.reload(); } }; } }); } })();

Detailed Explanation

We wrap our entire block of code inside of an anonymous function that we execute immediately. This technique keeps our local variables out of the global scope and offers some convenient encapsulation.

(function(){ //... })();

Since this code is theoretically in the autoload.js file, we have to assume it is called on every single page. In reality we only want to register this handler for the /app/ask page. We do this by grabbing the current page path, then using a RightNow Text utility to see if the path begins with "/app/ask". If it does, we register the event handler.

var path = window.location.pathname.toLowerCase();; /** * Handle Special tagging events on CONTACT US page */ if(RightNow.Text.beginsWith(path, "/app/ask")) { //... }

Now that we know we are on the correct page, we want to register an event handler for the on_before_ajax_request event. This event occurs whenever ANY AJAX call occurs through the RightNow Customer Portal framework. We pass in an anonymous function as our event handler. This event handler will modify the success handler without custom logic.

//Register Ajax Intercept RightNow.Event.subscribe("on_before_ajax_request", function(name, eo) { //... }

Even on the /app/ask page, there could be multiple AJAX events that are fired. For example, if there is an "email" field an AJAX call is made to validate its uniqueness. Same is true for the "login" field. If you use the Country/Province fields, an AJAX call is made whenever a Country is selected in order to load the appropriate Province values. We are not interested in any of these events. We only want to perform custom logic when the event is from a "Form Submit". The next bit of code checks the Event Object (passed into our event handler function) to confirm that the event is from a Form Submit; if it is, we proceed with modifying the success handler.

//Only for Form Submits. Other ajax events will occur //(product load, state/prov load, email val). //We don't want to modify their behavior. if(requestObj.scope && requestObj.scope.data && requestObj.scope.data.info && requestObj.scope.data.info.name === "FormSubmit") { //... }

We want to modify the success handler's behavior in a subset of cases. In some situations we want to call the default success handler. For this reason, we make a "copy" of the success handler by creating a prototype object and storing the original success handler on the prototype.

//Store default RN success handler on a prototype chain requestObj.prototype = {}; requestObj.prototype.successHandler = requestObj.successHandler;

Next we redefine the successHandler property on the Event Object with our own custom definition. When the AJAX call completes successfully, this function will be called instead.

//Define our own success handler. //This handler will prompt the user with a confirm dialog, then call the //default RN success handler if appropriate requestObj.successHandler = function(response, argument, transactionID) { //... };

Our custom implementation prompts the user using a JavaScript confirm dialog; we ask the user if they want to submit another Incident or not. For convenience, we also display the Incident reference number which is passed to our success handler function via the response parameter.

var self = this; var refno = response.result.refno; var result = confirm( "Your Incident was submitted. Reference Number: " + refno + "\nDo you want to submit another Incident?");

If the user "Cancels" the dialog, we want to perform the default action for Form Submit; this means directing the user to the Ask Confirmation page. We can do this by calling the original success handler which we stored in a previous step. If the user chooses "OK", we instead perform our custom logic and simply reload the page so that they can enter another Incident.

if(!result) { //Perform default action requestObj.prototype.successHandler.call(self, response, argument, transactionID); } else { //Reload the page so that the user can submit another event window.location.reload(); }

Conclusion

While the above use case if fairly simplistic, it gives a concrete starting point for writing your own custom success handlers. Theoretically, you could also define your own error handler, but I've never encountered a use case for this. As a challenge, try using the above technique to fire a Google Analytics event before the user is redirected to the "Ask Confirm" page. See if you can figure out how to get the original form data in your success handler and use that as part of your Google Analytics Event call (for example, the Incident subject).

Comments

Hi Andy, thanks for the detailed article....just wondering how u r able to decipher args parameter .....
THanks
SUresh

Just use your browser's console logging features. Most non-shitty browsers (i.e. not Internet Explorer) have this built in.

//Register Ajax Intercept RightNow.Event.subscribe("on_before_ajax_request", function(name, eo) { console.log(name, eo); var requestObj = eo[0]; //get pointer });

Andy, My requirement is to include Custom Object fields in Ask.php so I am using Siteinfo example of CP provided by Oracle and but I am getting JSON not correctly formatted....below is what i have done

- removed formsubmit successhandler statement to submit via Action attribute of form tab but JSON Format error pops up
- tried customizing FormSubmit widget but its failing

- Now trying your solution but Would like to know how to put Custom Controller URL like /cc/AjaxSiteLocation/submitAsk in the above code in autoload.js

your help will be highly appreciatd

Thanks
SUresh

The code is similar; from my code the requestObj will have a property named 'url'. Just set that property to whatever URL you want to submit to.

EX:
requestObj.url = '/cc/AjaxSiteLocation/submitAsk';

Andy - Thanks for your response..I am literally pulling my hair .....After doing trial & error, I figured what u mentioned but its creating incident but not SiteInfo Custom Object. I would like to know from you:

- How to figure out what lines are being executed in AjaxSiteLocation controller?
- Why SiteInfo Custom Object is not being created? Recap of what I have done

Modified Ask.php
-------------------------------------------
<rn:meta title="#rn:msg:ASK_QUESTION_HDG#" template="standard.php" clickstream="incident_create"/>
<rn:widget path="knowledgebase/RssIcon2" icon_path="" />
<div id="rn_PageContent" class="rn_Live">
<div class="rn_Padding">
<h1>#rn:msg:SUBMIT_QUESTION_OUR_SUPPORT_TEAM_CMD#</h1>
<form id="rn_QuestionSubmit" method="post" action="" onsubmit="return false;">

<div id="rn_ErrorLocation"></div>
<rn:condition logged_in="false">
<rn:widget path="input/FormInput" name="contacts.email" required="true" label_input="Email address" initial_focus="true"/>
<rn:widget path="input/ContactNameInput" table="contacts" required = "true"/>
<rn:widget path="input/FormInput" name="incidents.subject" required="true" />
</rn:condition>
<rn:condition logged_in="true">
<rn:widget path="input/FormInput" name="incidents.subject" required="true" initial_focus="true"/>
</rn:condition>
<rn:widget path="input/FormInput" name="incidents.thread" required="true" label_input="#rn:msg:QUESTION_LBL#"/>
<rn:widget path="input/FileAttachmentUpload2"/>
<rn:widget path="input/ProductCategoryInput" table="incidents"/>
<rn:widget path="input/ProductCategoryInput" table="incidents" data_type="categories" label_input="#rn:msg:CATEGORY_LBL#" label_nothing_selected="#rn:msg:SELECT_A_CATEGORY_LBL#"/>
<rn:widget path="custom/input/SiteInfo"/>
<rn:widget path="custom/input/FormSubmitCustom" label_button="#rn:msg:CONTINUE_ELLIPSIS_CMD#" error_location="rn_ErrorLocation" on_success_url="/app/ask_confirm"/>
</div>
</form>
<rn:widget path="standard/input/SmartAssistantDialog" display_answers_inline="true"/>

</div>

-----------------------------------------------------------
autoload.js
-----------------
(function(){
var path = window.location.pathname.toLowerCase();;

/**
* Handle Special tagging events on CONTACT US page
*/
if(RightNow.Text.beginsWith(path, "/app/ask_co"))
{
//Register Ajax Intercept
RightNow.Event.subscribe("on_before_ajax_request", function(name, eo)
{
var requestObj = eo[0]; //get pointer
//alert(requestObj);
//Only for Form Submits. Other ajax events will occur
//(product load, state/prov load, email val).
//We don't want to modify their behavior.
if(requestObj.scope
&& requestObj.scope.data
&& requestObj.scope.data.info
&& requestObj.scope.data.info.name === "FormSubmit")
{
//Store default RN success handler on a prototype chain
//console.log(requestObj.scope.data);
requestObj.url = '/cc/AjaxSiteLocation/submitAsk';
requestObj.prototype = {};
requestObj.prototype.successHandler = requestObj.successHandler;

requestObj.successHandler = function(response, argument, transactionID)
{
RightNow.Url.navigate('/app/map');

};
}

});
}
})();
------------------------------------------
SiteInfo files are same as in the Sample provided in Oracle site....kindly guide me on this

If

I'm unfamiliar with these exact Oracle-provided examples, but from my experience working with Custom Objects via CP widgets and controllers require a ton of custom code; more code than I can likely address via this forum.

As for debugging, throw some print_r statements or \RightNow\Utils\Framework::logMessage calls into your AjaxSiteLocation controller's submitAsk method. These will allow you to log/view the values of the various variables during the execution of the method. Maybe something will stand out.

Thanks andy ill put print_r statements in ajax controller and then revert to u

Zircon - This is a contributing Drupal Theme
Design by WeebPal.