Detecting changed attributes when form save is complete - dynamics-crm-2013

Ultimately what I'm looking for is an onSaveComplete event.
The problem is that we have plugins that modify entity data when it is saved. For most attributes this is fine, because the data is updated on the form after the save completes. Client scripts are problematic though because (as far as I can tell) there are no events to indicate that a value was updated by a plugin when the entity was saved.
The best solution I have come up with is to hook into the onSave event, prevent the default save action, and use data.save() instead:
function onSave(context) {
var args = context.getEventArgs();
args.preventDefault();
Xrm.Page.data.save().then(function() {
//check for changed attributes here
});
}
(Actual code is a bit more complicated to prevent recursion, etc. but you get the idea)
Ultimately this approach ends up being quite messy, and the more forms I apply it to, the more it feels like a real hack. So my question is - is there a better, more standard way to approach this?

I accomplished something similar to a post-save JS event through a different kind of "hack".
Here's how it works:
Put ModifiedOn field on the form
Subscribe to ModifiedOn's OnChange event
The event you attach to the OnChange will fire every time the form is saved (because when data changes so does ModifiedOn).
It's very likely that ModifiedOn should be made read-only on the form, to avoid clashing scripts.

Related

Vue component loses input when asynchronous call updates another property

I have a component that has an <input> with a :value set to a certain property. The component has another property unrelated to this input, which can get updated asynchronously (AJAX call). Whenever you're typing inside the input and the asynchronous call finishes, updating the other property, your typed input is reset.
To recreate this problem I've created a jsfiddle using a setInterval to simulate the async call and increment the other passed property. Try typing in the input, it will get reset every second. If you're quick enough, you can tab out and cause the #change to trigger the actual update.
The question is: why is the update to the other prop invalidating/rerendering the component and how can I work around this?
Note that v-model="person.name" is not a valid solution here - I need to know the old and new value, which is why I'm using a manual :value/#change combo.
Edit: The updateName method also really only needs to be triggered when the user leaves the input field. This is because the code run inside it is relatively CPU intensive and only needs to run when the user is done with the input and leaves it (in my actual code, not the jsfiddle).
Edit2: Is there some way to not let it re-render the entire component, but only the relevant pieces?
Because the parent component is changing a property of the child component, it has to re-render (parts of) the child component. Since you are using #change, instead of #input, your changes are not saved yet to the reactive variable person.name, it only works if you click tab quick enough. One solution would be to change #change to #input (which better resembles v-model):
https://jsfiddle.net/mf67xq1e/
Another (better) option is to use v-model and use a watcher to retrieve both the old and the new value:
watch: {
// whenever person.name changes, this function will run
"person.name": function (newName, oldName) {
console.log("newName:", newName);
console.log("newName:", oldName);
}
}
https://jsfiddle.net/mf67xq1e/1/
EDIT:
As you mentioned you only need to trigger something when you blur/leave the input field, seperate the reactivity of the variable and the triggering of your other method (e.g. updating some other variable or something), in two seperate variables:
https://jsfiddle.net/ta9cgnx0/
EDIT 2: Cleaner option with v-model and a seperate call for your other trigger on blur:
https://jsfiddle.net/ay1g63u8/

see the list of event listeners currently attached

I want to check the list of event listeners that are added. For example, I used the code cy.on('pan zoom resize', update); and added function called update in for loop. I do this many times. I also call cy.off('pan zoom resize', update); to remove the event listeners but I want to be sure about it.
The only think I can think of is using console.log but this method might not be helpful.
I also think that in some places people forgot to remove the event listeners and just always added. With too many repetitions this might cause problems.
There is a data field in the private cytoscape object called listeners. You can see that if you:
console.log() the cy object,
navigate to _private,
then open the emitter object
and lastly go to listeners
This is the array listing all the default and user defined event listeners with some metadata like the event, type and scope of the listener.
You can access this in your code by simply calling
cy.emitter().listeners
The question now is, why do you need this information in the first place? Normally, you should be just fine if you call cy.off('eventXY', ...) before using any cy.on('eventXY', ...). Are you sure you need this for your application to work? Maybe elaborate more on the core problem (why you want these information in the first place).
Thanks and have a great day!

Load options on the first open of the Async drop down menu

When I provide loadOptions to an Async control it loads options on mount.
If I pass autoload={false} then it doesn't load options neither on mount nor on open. But it loads options on the first close (or type, or blur).
If I pass onCloseResetsInput={false} then it doesn't load options until I type something. (showing "Type to search" in the menu)
Async provides onOpen handler, but I didn't find the way to use it in this situation. (and react-select#2.0.0-alpha.2 doesn't have it)
So the user needs to type a character, then delete it, to see the full list of options.
How can this be avoided?
Example sandbox: https://codesandbox.io/s/mjkmowr91j
Solution demo: https://codesandbox.io/s/o51yw14l59
I used the Async options loaded externally section from the react-select repo.
We start by loading the options on the Select's onFocus and also set the state to isLoading: true. When we receive the options we save them in the state and render them in the options.
I also keep track of optionsLoaded so that only on the first focus do we trigger the call to get options.
In our use case, we have several of these select inputs on a single page, all async, so the requests to the server will pile up, and are completely unnecessary in a lot of cases (users won't even bother clicking).
I found a workaround for this issue that'll work for my use case on 2.0.0-beta.6:
Include defaultOptions
Add 2 members to your class that will store the resolve/reject methods for the promise.
In your loadOptions function, check if the input is '', if so, create a new promise, and store the values of resolve/reject within your class members, and return that promise. Otherwise, just return the promise normally to get your results.
Add an onFocus handler, and within it call the function to get your results, but also add .then and .catch callbacks passing the resolve and reject functions you stored previously.
Essentially, this makes react-select think you're working on getting the results with a long-running promise, but you don't actually even try to load the values until the field is selected.
I'm not 100% positive there aren't any negative side effects as I just wrote this, but it seems like a good place to start.
Hope this helps someone. I may submit a feature request for this.
In order to load options when user focus first time, set defaultOptions={true}
Thanks, Alexei Darmin for the solution, I was struggling with this... while testing it I converted the solution to a react functional component and added real API fetching.
Here is a working demo, I hope it helps someone

Dojo 1.4.2 Tree Grid "expando click" event? persist state?

Question:
Given a DOJO TreeGrid, how can I capture the event when a user clicks the expando ("+") button to expand a row, and store the specific row number or associated item's identifier? I'd like to do this for the express purpose of completely deleting the TreeGrid from the DOM, rebuilding it, and restoring it's state once rebuilt (i.e. programmatically expanding the rows that the user has previously expanded).
Background:
So I've got a custom DOJO TreeGrid, hooked up to a custom QueryReadStore, in my app. It was constructed using the following tutorial:
http://www.ibm.com/developerworks/web/library/wa-dojotreegrid/index.html?ca=drs-
Pretty interesting tutorial, but it might be irrelevant to my question because it doesn't really squash any functionality, it only seems to augment it.
Anyway, googling around for a moment, I found a nice function in the DOJO forums that I can use to programmatically expand a row, given the specific row index. Works perfectly.
The problem is that I haven't been able to find a good way to capture the expando click event, and relate it to a specific "parent item row" index in the grid.
Details aside, I'd like to get the row index of every row that the user has expanded, into an array (and delete the index of a row that the user collapses, obviously), so I can destroy this TreeGrid, and faithfully rebuild it, with the user's selections expanded properly.
I'm not really a novice to DOJO, but I'm certainly no expert by any means. I've done a fair bit of googling, and FireBugging, and haven't really been able to find anything that I can use to do this.
Suggestions? Anybody done something similar before? Stupid question with obvious answer that I've missed? I'm totally misguided and am going about it all wrong? Thanks everybody!
Something similar to this would probably work, this is how the dijit.Tree implementation wouldve looked;
var expandedNodes = {}
dijit.tree._onExpandoClick = function (args /* object wrap for args.node */) {
var treeNode = args.node,
path = treeNode.getTreePath(),
id = treeNode.getIdentity();
expandedNodes[id] = path;
}
I am not 100% sure im being strictly correct but for the TreeGrid you will have to look through code of dojox/grid/_TreeView.js (link). The _setOpen would be an entrypoint from which you can 'hook' the onclick action. from there, find the expandoCell.openStates hash, this is a true/false variable set, indexed by itemId. This hash is most likely what you need as your state

Using Dojo to reenable a submit button when a form is edited

I'm trying to build a web app using Dojo. I have a form that posts data via Dojo's xhrPost capabilities to a server side program that saves changes made on the form whenever the user hits the "save" button. What I would like to do is disable the save button after a successful save until the next time something is changed in any of the form's fields to avoid repeated attempts to save an unchanged document.
I tried having Dojo's event watching functionality watch for changes, but have not been successful. The event intended to trigger reenabling the save button never does anything. Here is what I tried:
eventWatching.push(dojo.connect(dijit.byId('editForm'), 'onChange', function() { dijit.byId('saveButton').set('disabled', false); }));
Using onKeyPress instead of onChange seemed promising, but that did not (obviously) reenable the button when the form was edited using the mouse alone.
Prior to 1.6, I don't think dijit.form.Form connects its children's onChange to its own, which is likely why your onChange idea didn't work.
In Dojo 1.6, what you're asking for is easily possible by taking advantage of the fact that widgets now inherit dojo.Stateful functionality:
form.watch('value', function(property, oldvalue, newvalue) {
/* ... */
});
In 1.5 or lower this might take some work; can't think of an easy way off the top of my head but maybe someone else has an idea or one will hit me later.
You can find the code responsible for hooking up the onChange and value-watching functionality in 1.6 here: https://github.com/dojo/dijit/blob/master/form/_FormMixin.js#L396-429
If the newvalue is emptyString the form is valid.
frm1.watch('state',function(property, oldvalue, newvalue) {
console.log(newvalue)
})