I am writing javascript code to change the form of a entity in Dynamics CRM based on the value of a field on each form.
To change the form, the user has to change the value of the field.
Then during the onChange event, my js comes in, triggers saving, has to wait for the result and then change the form. (If you save and change at the same time, there is still a window shown asking the user to confirm leaving unsaved changes)
Now there should be a way to do that:
Xrm.Page.data.save(saveOptions).then(successCallback, errorCallback)
as it is described on msdn:
Saves the record asynchronously with the option to set callback functions to be executed after the save operation is completed.
I am using it as such:
var campaignType = Xrm.Page.getAttribute('typecode').getValue();
if (xxx.Forms.hasOwnProperty(campaignType)) {
Xrm.Page.data.save().then(function () { xxx.redirectToForm(xxx.Forms[campaignType]); }, null);
But the form change is still triggered immediately during the save.
What am I doing wrong?
I faced a similar problem while trying to update the process bar.
Xrm.Page.data.save().then
(function () {
window.location.reload(true);
},
function () {
windows.alert("broken");
}
);
I strongly suggest you to try to apply the logic on a vanilla CRM, for me what was breaking the logic was a third party component called N52 Rules, their code was interfering with the callback forcing the refresh of the page before the save event. Your code seems correct.
Hey the Save and Refresh Calls are Asynchronous! that is why it hits the success handler immediately.
What you can try is using SDK.REST.js file for CRM
function updateFunction(entityId) {
var campaignType = Xrm.Page.getAttribute('typecode').getValue();
if (xxx.Forms.hasOwnProperty(campaignType)) {
var entity= {};
entity.typecode= campaignType;
SDK.REST.updateRecord(
entityId,
entity,
entityName, //"Account"
function () {
writeMessage("The record changes were saved");
xxx.redirectToForm(xxx.Forms[campaignType]);
},
null
);
}
}
https://msdn.microsoft.com/en-us/library/gg334427(v=crm.7).aspx
Here you can call updateFunction given above onChange and in the onSuccess handler you can try calling the form you want to call. I haven't tried it the way you want, but let me know if it works.
check this link out as well
https://msdn.microsoft.com/en-us/library/gg334720.aspx#BKMK_entityOnSave
Related
When I register an OnChange function to a field, it gets fired if the field is updated on a plugin on the server side (On Dynamics Crm 2015)
Can this behavior be prevented (In a supported way)?
Example code:
1. Client side:
Xrm.Page.getAttribute("org_myfield").addOnChange(function () { alert("org_myfield was changed") });
2. Server side:
internal void OnPreUpdateRequest(org_myentity target, org_myentity preImage)
{
target.org_myfield = "some value";
}
3. Result (after saving the record and plugin finished its run):
"org_myfield was changed"
Required result: The alert should not be fired.
I believe this is what you are trying to get:
1. When user makes change on this field, call the JavaScript on change function;
2. When the record is saved, a plugin might change the value of the field as well;
3. If the field value is altered by the plugin, skip the JavaScript function.
If this is correct, I would try this way:
Remove the onChange event from the field, instead check the field status in onSave event of the form.
Use Xrm.Page.getAttribute("org_myfield").getIsDirty() to determine whether the user makes any change on the field. It only checks the changes on client side so it won't be affected by plugin.
The onChange event for a field on my opportunity form is being called twice and I'm trying to track down the source of the second call. I've already passed the execution context into the onChange function but don't know any way to see the source of the call from there. Now I'm wondering if there's a way to see the pending events for an XRM page, does anyone know where that information is?
My code is basically this, tied to onLoad of the opportunity form. There could be a plugin or 3rd party library or something updating the field, but nothing jumps out.
function onLoad() {
Xrm.Page.data.process.addOnStageChange(handleStageChange);
}
function handleStageChange() {
var dateFieldName = "new_enteredstage1"
var dateFieldAttr = Xrm.Page.getAttribute(dateFieldName);
if (dateFieldAttr) {
dateFieldAttr.setValue(new Date());
}
}
If new_enteredstage1 is null when the stage change occurs then 2 calls occur and the value is set and immediately reset to null. If new_enteredstage1 has a value the value is updated as expected. Again, there could be some third party code that I'm missing but I have no idea how to track it down.
UPDATE:
This only happens on date fields, and it happens on all date fields. If I replace the code with a number field the value is not reset to null.
There's a know issue (example) related to the "new" form rendering engine (Turbo Forms) that can cause this issue.
You can try using the legacy form rendering engine and see if it solves the problem:
I have an app where you can enter some information about a customer. I have the problem that this information easily can be overwritten by other changes in the app.
I want:
A textarea showing the information
The message shown should update as changes are made to it in other places in the app
If the user has changed the message in the textarea, the message in the textarea should NOT be overwritten by server side changes or other changes in the app
When a save button is pressed, the model is updated
Simply doing this:
<textarea ng-model="customer.info"></textarea>
<button ng-click="save(customer)">Save</button>
won't work, because if something is changed to the customer object on the server, the information field will be reset and the user's changes will be overwritten.
Update:
In response to your comment/updated question: I'd simply track the state of the textarea using a couple of event listeners, that check if the user changed the value. If so: the textarea is no longer kept in sync with the server data.
I also check if the textarea has focus, if that is the case, the value should not be updated, because the user might be editing its contents.
Lastly, I've attached a blur event handler, that checks the value again, in case the user "undoes" changes. If a user adds a char by accident, and removes it later on, the textarea's value might be the same as the last known value that came from the server, in which case, the textarea's value should be kept in-sync again.
I've set up a simple fiddle that uses an interval to do all this. Replace the interval-related code with your worker, ajax callback or whatever it is you're using. If it's a worker, unsetting the interval should be replaced with unbinding the onmessage handlers with a handler function that syncs the textarea or doesn't sync the textarea. Alternatively use a single function that checks the changed flag whenever it wants to change the textarea's value. Plain and simple.
Here's the fiddle
And here's the code (the fiddle version contains some actual comments that explain various bits and pieces):
(function (txt)
{
var initialValue = txt.value,
changed = false,
id,
callback = function()
{
if (changed === false)
initialValue = (txt.value += ' Server-sync');
else
{
clearInterval(id);
id = null;
}
};
txt.addEventListener('change', function()
{
if (this.value !== initialValue)
changed = true;
}, false);
txt.addEventListener('focus', function()
{
changed = true;
}, false);
txt.addEventListener('blur', function()
{
if (this.value !== initialValue)
changed = true;
else
{
changed = false;
id = setInterval(callback, 5000);
}
}, false);
id = setInterval(callback,5000);
}(document.querySelector('#foo')));
Initial answer
Like I said in my comment:
<textarea ng-model="customer.info" id='foo' readonly></textarea>
prevents the value of the textare from being altered by the user, but you can still set/change its value in your JS code:
var changeTxt = (function(txt)
{
return function(addVal)
{
txt.value += addVal;
};
}(document.querySelector('#foo')));
Demo
To keep the value of the textArea in sync, you could perform an AJAX call prior to making any changes to the value client side, or create a worker "thread" that polls the server for changes that have been made since the value was last fetched.
Seeing as you're using angular.js, I must admit that I don't have any experience with how angular can be best used to solve your issue, but a quick google search lead me to 3-way data binding, which does look rather promising.
I am working on a function which allows users to sign in on my website with their Google account.
My code is based on the Google documentation (others signIn() options are in meta tags).
function login() {
gapi.auth.signIn({'callback':
function (authResult) {
if (authResult['status']['signed_in']) {
console.log('Okay');
}else {
console.log('Error');
}
}
});
}
When I call login(), a Google pop up appears, I approve the terms of my application and everything works fine.
But the callback is called twice :
1st case: If I never approved apps permissions then the callback will be call at the opening of the pop up AND when I will approve the permissions. So it will write "Error" and "Okay".
2nd case: If I already approved the permissions, it will write "Okay" two times.
I added the option 'approvalprompt': 'force' to the signIn() function. The callback function is no longer called twice but it forces the user to approve the app's permissions, even if previously approved. So it's not user friendly.
Is there a friendly user way to approve the app's permissions one time without having two callback ?
Thank you.
I'm facing this same issue here, but I'm calling gapi.auth.signIn() via a button click handler. The callback is still called twice. One thing I noticed between the two authResult objects was that authResult.status.method is 'AUTO' in the first call (before the popup window appears) and is 'PROMPT' in the second call after the window is auto-dismissed due to previous authorisation.
The solution I'm exploring now is to ignore the AUTO instance and only process the PROMPT instance of the callback. Not sure how this will work once I revoke the permissions within Google due to the lack of details in the docs on the 'status' object.
I am facing the same issue: signin callback called twice in case of user that already granted permission; the local variable approach (initializedGoogleCallback) isn't working for me because it call the callback one time only when the user already granted access, but didn't call it if the user is the new one.
After a bit of research (i especially dig in site using the g+ auth) i noticed that all of them use the 'approvalprompt': 'force' and they have the already granted user to reapprove a "Offline Access" policy everytime.
Even the google example i followed to setup my app (https://developers.google.com/+/web/signin/javascript-flow) even if it did not mention it, it uses the "force" parameter.
For the moment it seems the only solution if you want to use the javascript flow (that mean if you need a personal style signin button)
Try to register first call in some local variable and then process it
This quick solution helps me:
function login() {
var initializedGoogleCallback = false
gapi.auth.signIn({
'callback': function (authResult) {
if (!initializedGoogleCallback) {
// after first call other will be ignored
initializedGoogleCallback = true;
if (authResult['status']['signed_in']) {
console.log('Okay');
} else {
console.log('Error');
}
}
}
});
}
also you can add following code before call gapi.auth.signIn
window.removeEventListener('load')
Like the Drew Taylor's answer, to avoid the double callback with the pure javascript sign in solution, you can check the user's session state:
if (authResult["status"]["method"] == "PROMPT") {...}
I think that the callback with the AUTO method is fired by the bottom welcome bar that appears on first login.
That is the intentional plan for page level config! It being present in the page causes the callback to fire when the Javascript is finished loading. What you should do is prepare for that in your code.
Don't show the sign in button until you have received a callback - if authResult['status']['signed_in'] == true, then treat the user as signed in (setup a session etc, whatever you would normally do). If it is false, then display the button.
function signinCallback(authResult) {
if (authResult['status']['signed_in']) {
document.getElementById('signinButton').setAttribute('style', 'display: none');
// Do sign in stuff here!
} else {
document.getElementById('signinButton').setAttribute('style', 'display: block');
}
}
I would avoid using approval prompt force if you can!
finally i solved with a workaround; i don't know if this is the correct way to approach or i am just cheating but i do this way:
first of all some script in the page (i am using bootstrap + jquery)
function render() {
//I am not using it but kept anyway
}
var i;
// Function called from a onClick on a link or button (the 'sign in with g+' button)
function gp_login() {
i=0;
$('#alertbox').remove();
var additionalParams = {
'callback': signinCallback,
/*'approvalprompt': 'force' finally removed*/
};
$('#gp-login').button('loading');
gapi.auth.signIn(additionalParams);
}
function signinCallback(authResult) { //my callback function
var email='';
var given_name='';
if (authResult['status']['signed_in']) { //get some user info
gapi.client.load('oauth2', 'v2', function() {
gapi.client.oauth2.userinfo.get().execute(function(resp){
email = resp.email; //get user email
given_name = resp.given_name; //get user email
family_name=resp.family_name;
id=resp.id;
if (i<2) { //execute the doLogin just one time (my cheat)
doLogin(email,given_name,family_name,id); //dologin does my logic with an ajax call to signup/register user to my site
}
i=2;
});
});
} else {
// Update the app to reflect a signed out user
}
}
this approch have the doLogin part called just one time, but the callback is called twice (gapi.client.oauth2.userinfo.get() this function is called twice); with a bit more tweaking with the if / var check i think is possible to call everything once.
This way if the user already granted the auth, it will be automatically signed.
I notice that sometimes google have a popup layer on the bottom of layer showing a "welcome back message", but i didn't understand when it appears or if i have to call it manually
I have a Flex application which allows the user to edit a cloud-based document. (Think SlideRocket.) When the user tries to navigate away or close the browser window, I'd like to show them an are-you-sure dialog iff they have unsaved changes.
I'm using the following custom class, which I found at Flash player notified on browser close or change page (as3). I don't think it is the problem.
package
{
import flash.external.ExternalInterface;
public class ExternalInterfaceUtil
{
public static function addExternalEventListener(qualifiedEventName:String, callback:Function, callBackAlias:String):void
{
// 1. Expose the callback function via the callBackAlias
ExternalInterface.addCallback( callBackAlias, callback );
// 2. Build javascript to execute
var jsExecuteCallBack:String = "document.getElementsByName('"+ExternalInterface.objectID+"')[0]."+callBackAlias+"()";
var jsBindEvent:String = "function(){"+qualifiedEventName+"= function(){"+jsExecuteCallBack+"};}";
// 3. Execute the composed javascript to perform the binding of the external event to the specified callBack function
ExternalInterface.call(jsBindEvent);
}
}
}
In my applicationComplete function, I add an event listener to the javascript window.onbeforeunload event, as follows:
ExternalInterfaceUtil.addExternalEventListener("window.onbeforeunload", requestUnloadConfirmation, "unloadConfirmation");
The Actionscript function requestUnloadConfirmation (below) is successfully called when the user tries to close the browser window. However, it does not prevent the browser from closing. (In Chrome, the browser closes and the Actionscript function is called subsequently. In Firefox, the browser stays open for the duration of the function but then closes.)
private function requestUnloadConfirmation():String {
if (changedSinceSave)
return "There are unsaved changes. Are you sure you want to leave without saving?";
else
return null;
}
Behavior is identical in both debug and release builds, and on the production server as well as the local machine.
Any help would be greatly appreciated,
Dave
Right now, when the JavaScript event is fired, it is set to call your function in your AS3 code, which it does. The JavaScript function, however, is not returning the value that your AS3 function returns. To get this behaviour, add 'return' to the JavaScript event-handling function created in addExternalEventListener like so:
var jsBindEvent:String = "function(){"+qualifiedEventName+"= function(){return "+jsExecuteCallBack+"};}";
Since the event handler should return a true or false value, your requestUnloadConfirmation function should have a return type of Boolean and return false to cancel the event, and true otherwise. Use the following to get a confirmation dialog box:
private function requestUnloadConfirmation():Boolean {
if (changedSinceSave)
return ExternalInterface.call("confirm", "There are unsaved changes. Are you sure you want to leave without saving?");
else
return false;
}
UPDATE:
It turns out that returning a string to window.onbeforeunload causes a confirmation dialog box to be shown automatically. The ExternalInterface.call to confirm causes a second dialog box to show; it is redundant. The only change required in the AS3 code is to add the "return" in the generated JavaScript.
In a regular html/javascript web-app you would use the window.onbeforeunload event to do this.
https://web.archive.org/web/20211028110528/http://www.4guysfromrolla.com/demos/OnBeforeUnloadDemo1.htm
Perhaps you can use this event, and check some value of your flex app to determine if you should ask the user (not familiar with flex...)?
I had to run some modifications in the above code to make it work.
The problem was due to the fact that the function which handles the event window.onbeforeunload, should not return any value to avoid popup confirmation and should return text value when a popup confirmation is in-order
Here are my changes:
private function requestUnloadConfirmation():String {
if (changedSinceSave){
return "There are unsaved changes. Are you sure you want to leave without saving?";
}
return null;
}
And a little change in embedded JS
var jsBindEvent:String = "function(){"+qualifiedEventName+"= function(){ if ("+jsExecuteCallBack+") return "+jsExecuteCallBack+"};}";