Update Dynamics form field when item is selected on another field - dynamics-crm

I have a Dynamics entity that has a relationship to another entity with a Main form in UX. I have a lookup control on the form to select that related entity. Doing so correctly writes the ID of that related entity record to the current record being edited. I need help grabbing the name value of that linked entity and writing it to the name value of the current record when the lookup selection changes. The form designer makes this so much more difficult than it needs to be! Is there a set of basic jscript libraries that I can add to my solution and call for such simple tasks?

After literally HOURS of hunting down bits and pieces, I finally got this to work!
function updateName(){
var name = "";
var lookupField = Xrm.Page.getAttribute("lookupentityproperty");
// Verify the field does exist on the form and has a selected value
if (lookupField != null && lookupField.getValue() != null && lookupField.getValue()[0] != null) {
name = lookupField.getValue()[0].name;
}
else { name = null; }
var nameField= Xrm.Page.getAttribute("nameproperty");
nameField.setValue(name);
}

Related

Dynamics 365 API link between ActivityPointer and activitytypecode global option set

I am reading data from the ActivityPointer entity in Dynamics 365 via the API and I want to link the activitytypecode field value to the activitypointer_activitytypecode global option set, which I believe is the correct one. However the values don't seem to match. In the ActivityPointer.activitytypecode field I have values such as:
phonecall
bulkoperation
email
appointment
task
But those values don't appear in the option set definition, using this query: GlobalOptionSetDefinitions(Name='activitypointer_activitytypecode')
The option set has the code values (e.g. 4202 for Email) and the different descriptions in all languages, but nothing matches back to the values on ActivityPointer
Optionset is just key value pairs (4202: Email and so on), If you want to get the formatted text value of optionset (Email, Fax, etc) from your web api query results - then you have to use activitytypecode#OData.Community.Display.V1.FormattedValue to get it. Read more
I recommend this article for complete understanding of CRM activities.
If you are looking for the code integer value in your resultset, that seems to be an issue and the result is not the expected one - old SO thread
The problem is that if you are reading activitytypecode in code, then you will know that you get a string value. This is the logical name of the activity entity, e.g. "email", "phonecall" etc.
If you look at the definition of activitytypecode in Power Apps then it shows it as "Entity name" (i.e. text) but using the classic solution editor it shows as the global activitypointer_activitytypecode option set, which contains values for "Email", "Phone Call" etc.
I am sure that there should be a simple way of converting from activitytypecode (i.e. entity name) to activitypointer_activitytypecode (i.e. option set), but I've yet to find it.
What I am doing is retrieving the global activitypointer_activitytypecode option set, so I have access to all of the text values. Then retrieve details about the entity indicated by activitytypecode, specifically what is of interesting is the display name. Then loop through the option set looking for a case-insensitive match on display name.
This is my C# code:
public int? GetActivityType(IOrganizationService service, string activityTypeCode)
{
// Get all activity types.
var optionSetRequest = new RetrieveOptionSetRequest()
{
Name = "activitypointer_activitytypecode"
};
var optionSetResponse = (RetrieveOptionSetResponse)service.Execute(optionSetRequest);
var optionSetMetadata = (OptionSetMetadata)optionSetResponse.OptionSetMetadata;
var optionValues = new Dictionary<string, int?>(StringComparer.OrdinalIgnoreCase);
foreach (var option in optionSetMetadata.Options)
{
foreach (var optionLabel in option.Label.LocalizedLabels)
{
optionValues[optionLabel.Label] = option.Value;
}
}
// Get the display name for the activity.
var retrieveEntityRequest = new RetrieveEntityRequest
{
EntityFilters = EntityFilters.Entity,
LogicalName = activityTypeCode
};
var retrieveEntityResponse = (RetrieveEntityResponse)service.Execute(retrieveEntityRequest);
LocalizedLabelCollection entityLabels = retrieveEntityResponse.EntityMetadata.DisplayName.LocalizedLabels;
// Look up the display name in the option set values.
foreach (var entityLabel in entityLabels)
{
if (optionValues.TryGetValue(entityLabel.Label, out int? value))
{
return (Schema.GlobalOptionSet.ActivityType?)value;
}
}
// If we get here then we've failed.
return null;
}
That is making two API calls, so best avoided in any situations where performance might be an issue. I'm not saying the code is perfect, but it hasn't let me down yet. Even so, I would recommend making do with the logical names provided by activitytypecode if you can.

Is there a way to make a field in Dynamics CRM as one time entry field?

Basically, what I want is that the user should be able to select the dropdown and not be able to change it after making and saving the selection. So it will be a one-time entry field.
Below is a screenshot of the field I want to apply this property.
So this field has a Yes or No selection. And to make the business logic from failing I have to make it a one-time entry field only.
I looked up the form editor for possible things but couldn't find anything that would let me achieve this.
UPDATE #1
Below is my onload function:
function Form_onload() {
var formType = Xrm.Page.ui.getFormType();
var p = Xrm.Page.getAttribute("opportunityid");
--------------NEW CODE--------------------------------
if(formType ==2){ //form type 2 means the form is a saved form. form type 1 is new form.
alert(formType);
var myattribute = Xrm.Page.getAttribute("var_internal");
var myname = myattribute.getName();
if (Xrm.Page.getControl(myname) != null) {
//alert(myname);
Xrm.Page.getControl(myname).setDisabled(true);
}
}
--------------NEW CODE---------------------------
if (formType == 1 && p != null && p.getValue() != null) {
alert('Child Opportunities can only be created by clicking the Create Child Opportunity button in the Opportunity ribbon.');
window.top.close();
}
}
No code solution: I think you could use an Option set with a Yes/No option and a default of Unassigned Value. Then add that field to Field Level Security with "Allow Update" set to No.
When updating the FLS field permissions, be sure that the profile is associated with the organization "team" so that all users can see the field:
Arun already gave you an hint how to proceed, I just tried this req on one of my instance.
Create one extra field (dummy field) in my case I call it new_hasfieldbeenchanged1
This field will hold data when field is changed. Lock this field (always) and keep this field on form (but visibile =false)
Now you need 2 trigger Onload and OnSave. Below function will do your work. Let me know if this helps.
function onLoad(executionContext) {
debugger;
var formContext;
if (executionContext && executionContext.getFormContext()) {
formContext = executionContext.getFormContext();
//executionContext.getEventSource()
if (formContext.getAttribute("new_hasfieldbeenchanged1") && formContext.getAttribute("new_hasfieldbeenchanged1").getValue()!=null) {
if (formContext.getControl("new_twooptionfield")) {
formContext.getControl("new_twooptionfield").setDisabled(true);
}
}
}
}
function onSave(executionContext) {
debugger;
var formContext;
if (executionContext && executionContext.getFormContext()) {
formContext = executionContext.getFormContext();
//executionContext.getEventSource()
if(formContext.getAttribute("new_hasfieldbeenchanged1") && formContext.getAttribute("new_twooptionfield") && formContext.getAttribute("new_twooptionfield").getIsDirty()){
formContext.getAttribute("new_hasfieldbeenchanged1").setValue((new Date()).toString());
if (formContext.getControl("new_twooptionfield")) {
formContext.getControl("new_twooptionfield").setDisabled(true);
}
}
}
}
Due to environment-specific settings in my DEV, I was not able to reproduce what was suggested by #Eccountable. Although, his solution worked in other environments.
#AnkUser has a good answer as well but I was looking to shorten the code and make things as simple as possible.
My solution
I was able to handle this using Javascript on client-side. using the XRM toolbox.
In the XRM toolbox, I located the javascript for the opportunity and observed field changes in formType when the Opportunity was New and when the Opportunity was Existing. This variable (formType) was =1 when the Opportunity was New and =2 when it was Existing/Saved.
Using this piece of information I was able to leverage my Javascript as follows in Form_onload()
function Form_onload() {
if (formType == 2) {
var myattribute = Xrm.Page.getAttribute("internal");
var myname = myattribute.getName();
if (Xrm.Page.getControl(myname) != null) {
Xrm.Page.getControl(myname).setDisabled(true);
}
}
}
There’s no OOB configuration for such custom requirements, but we can apply some script logic.
Assuming this is a picklist with default null and not a two-option bool field, we can use onLoad form script to check if it has value & lock it. No need to have onChange function.
If it’s a bool field, then it’s hard to achieve. You have to track the initial value & changes made to implement the logic you want. Or through some unsupported code.

Why can't my Sitecore custom validator read the item as it has been edited in Content Editor?

I have created a custom validator in Sitecore. It executes correctly, and I can debug it and step through it.
I extended my validator from StandardValidator, which in turn extends from BaseValidator.
In my validator, I get the item:
var item = GetItem();
GetItem is a method from BaseValidator. Based on my reading, this is the standard method to get the item to be validated.
The problem: the item I get back doesn't reflect the values that have been edited in Content Editor. Instead, it appears to simply be the item from the database (so, what it looked like when it first loaded in Content Editor, without reflecting any changes that might have been made to it).
Thus, I cannot validate. To validate the data an editor might have changed in Content Editor, I need to be able to see those changes. Seeing how the item sits in the database right now doesn't help me.
I did some extended debugging, which may or may not be helpful --
I decompiled the kernel, and walked through both StandardValidator and BaseValidator.
The GetItem method from BaseValidator does what I suspect -- it gets the item from the database. But then it runs it through a method called UpdateItem which appears be intended to overlay the inputted values from Content Editor on top of the stored values (thus returning an object that accurately reflects the data which is currently sitting in the editor). This is clearly what I want.
However, UpdateItem only appears to overlay anything if a property called ControlToValidate has a value...and it never does. I've tried this validator as both a Field Validator and an Item Validator. I've initiated it by both saving an item and by tabbing off a field. I put a breakpoint in Visual Studio and a watch on ControlToValidate. One time (inexplicably) it had a value of FIELD165901047 (which corresponded to a field ID in content editor), but in all other cases, it's been null.
What this means is that UpdateItem effectively does nothing, and the item is simply returned as it currently sits in a database, which doesn't help -- I'm trying to validator values entered in Content Editor before saving to the database.
Regardless of my investigation (I think I understand UpdateItem, but I concede that I might be misinterpeting it, and I'm not accounting for potential decompilation errors), I still have this basic problem:
var item = GetItem();
That never seems to return the values from Content Editor. It returns the item directly from the database, which doesn't help me.
In my validator (a field type validator) I've used
var field = this.GetField();
to get new value of the field to validate. The same should work for a field validator. It's probably a bit different for an item validator but I've never written an item validator.
It's also a method of the BaseValidator and returns a value of the type Field. You can assign this directly to the type of the target field you want, e.g. HtmlField (Sitecore has implemented the casting operators so you can do this):
HtmlField htmlField = field;
I managed to create a work-around for this, and it seems to be working for me. I'm not that happy that I don't know why controlToValidate never has a value, but my workaround manages to get the new value that hasn't yet been saved.
Basically, I've overridden the code that runs GetItem(), and I find the new value in the page's SaveArgs as shown below. The magic happens on the line that has:
field.Value = ((((SaveArgs) ((ClientPage) page)?.CurrentPipelineArgs)?.Items[0].Fields) ?? Array.Empty<SaveArgs.SaveField>()).FirstOrDefault(x => x.ID == field.ID)?.Value ?? field.Value;
protected override Item GetItem()
{
var obj = base.GetItem();
if (obj == null || obj.Versions.Count == 0)
return null;
UpdateItem(obj);
return obj;
}
private void UpdateItem(Item item)
{
Assert.ArgumentNotNull(item, nameof(item));
using (new SecurityDisabler())
item.Editing.BeginEdit();
var page = (Page)null;
var current = HttpContext.Current;
if (current != null)
page = current.Handler as Page;
foreach (Field field in item.Fields)
{
var controlToValidate = ControlToValidate;
if (string.IsNullOrEmpty(controlToValidate) || field.ID != FieldID)
{
field.Value = ((((SaveArgs) ((ClientPage) page)?
.CurrentPipelineArgs)?.Items[0].Fields) ?? Array.Empty<SaveArgs.SaveField>()).FirstOrDefault(x => x.ID == field.ID)
?.Value ?? field.Value;
}
else
{
var str = current != null ? RuntimeValidationValues.Current[controlToValidate] : null;
if (page != null && str != null)
{
var control = page.FindControl(controlToValidate);
if (control != null)
{
if (control is IContentField contentField)
str = contentField.GetValue();
else if (ReflectionUtil.GetAttribute(control, typeof(ValidationPropertyAttribute)) is ValidationPropertyAttribute attribute)
str = ReflectionUtil.GetProperty(control, attribute.Name) as string;
}
}
if (str == null && current != null)
str = current.Request.Form[controlToValidate];
if (str != null && str != "__#!$No value$!#__")
field.Value = str;
}
}
}

How can I create temporary records of Linq-To-Sql types without causing duplicate key problems?

I have code that generates records based on my DataGridView. These records are temporary because some of them already exist in the database.
Crop_Variety v = new Crop_Variety();
v.Type_ID = currentCropType.Type_ID;
v.Variety_ID = r.Cells[0].Value.ToString();
v.Description = r.Cells[1].Value.ToString();
v.Crop = currentCrop;
v.Crop_ID = currentCrop.Crop_ID;
Unfortunately in this little bit of code, because I say that v.Crop = currentCrop,
now currentCrop.Crop_Varieties includes this temporary record. And when I go to insert the records of this grid that are new, they have a reference to the same Crop record, and therefore these temporary records that do already exist in the database show up twice causing duplicate key errors when I submit.
I have a whole system for detecting what records need to be added and what need to be deleted based on what the user has done, but its getting gummed up by this relentless tracking of references.
Is there a way I can stop Linq-To-Sql from automatically adding these temporary records to its table collections?
I would suggest revisiting the code that populates DataGridView (grid) with records.
And then revisit the code that operates on items from a GridView, keeping in mind that you can grab bound item from a grid row using the following code:
public object GridSelectedItem
{
get
{
try
{
if (_grid == null || _grid.SelectedCells.Count < 1) return null;
DataGridViewCell cell = _grid.SelectedCells[0];
DataGridViewRow row = _grid.Rows[cell.RowIndex];
if (row.DataBoundItem == null) return null;
return row.DataBoundItem;
}
catch { }
return null;
}
}
It is also hard to understand the nature of Crop_Variety code that you have posted. As the Crop_Variety seems to be a subclass of Crop. This leads to problems when the Crop is not yet bound to database and potentially lead to problems when you're adding Crop_Variety to the context.
For this type of Form application I normally have List _dataList inside form class, then the main grid is bound to that list, through ObjectBindingList or another way. That way _dataList holds all data that needs to be persisted when needed (user clicked save).
When you assign an entity object reference you are creating a link between the two objects. Here you are doing that:
v.Crop = currentCrop;
There is only one way to avoid this: Modify the generated code or generate/write your own. I would never do this.
I think you will be better off by writing a custom DTO class instead of reusing the generated entities. I have done both approaches and I like the latter one far better.
Edit: Here is some sample generated code:
[global::System.Data.Linq.Mapping.AssociationAttribute(Name="RssFeed_RssFeedItem", Storage="_RssFeed", ThisKey="RssFeedID", OtherKey="ID", IsForeignKey=true, DeleteOnNull=true, DeleteRule="CASCADE")]
public RssFeed RssFeed
{
get
{
return this._RssFeed.Entity;
}
set
{
RssFeed previousValue = this._RssFeed.Entity;
if (((previousValue != value)
|| (this._RssFeed.HasLoadedOrAssignedValue == false)))
{
this.SendPropertyChanging();
if ((previousValue != null))
{
this._RssFeed.Entity = null;
previousValue.RssFeedItems.Remove(this);
}
this._RssFeed.Entity = value;
if ((value != null))
{
value.RssFeedItems.Add(this);
this._RssFeedID = value.ID;
}
else
{
this._RssFeedID = default(int);
}
this.SendPropertyChanged("RssFeed");
}
}
}
As you can see the generated code is establishing the link by saying "value.RssFeedItems.Add(this);".
In case you have many entities for wich you would need many DTOs you could code-generate the DTO classes by using reflection.

How do you model form changes under Spring MVC?

Say you're writing a web page for fruit vendors using Spring MVC's SimpleFormController, version 2.5.6. On this page the vendor can do simple things like change their name or their address. They can also change their inventory based on a drop down list filled with present inventory selections.
When this drop down list selection changes, the entire form changes to match the inventory of what has been selected. So one stock selection may have bananas and pears, another may have melons, blueberries and grapefruit.
Inside each inventory selection is a input field that needs to be propagated back to the database, for the sake of this example let's say that the user enters the number of fruit.
The way this is modeled in the database is that each Stock name is stored in a table, which has a one to many relationship with the contents of each stock, which would be the type of fruit in this example. Then the type of fruit has a one to many relationship with the quantity the vendor selects. Stock name and the type of fruit in each stock are stored in the database and are unchangeable by the user, with the connected fruit quantity table being editable.
My question is, how do you model the form described above in Spring MVC?
I've tried overriding the isFormChangeRequest and onFormChange to facilitate the form change, but I think I may be misunderstanding the intent of these methods. When I change my backing command object the next time the page is post it tries to bind the request into the form, which breaks if you adjust the size of the Stock array (say from 3 to 2, it will try and bind into the 3rd value, even if it is empty).
If you have a limited amount of different stocks, you can use different handler mappings for each one with a different backing model:
#RequestMapping(params="stock=example1")
ModelAndView handleExample1(#ModelAttribute("stock") ApplesOrangesPears stockObject)
#RequestMapping(params="stock=example2")
ModelAndView handleExample2(#ModelAttribute("stock") BananasPotatos stockObject)
But I guess that is not the case, there are a lot of different stock types and they are dynamic. In that case you can register custom property editor (#InitBinder), and determine dynamically the actual type of the backing object for the inventory, then validate, and convert to or from it explicitly.
What I ended up doing is firing a JavaScript event when the selection in the drop down is changed. This JavaScript (seen below) generates a URL based on the selection of the drop down and uses a location.replace to go to the new URL, which causes the controller to generate a new form.
Using this method over overriding the isFormChangeRequest and onFormChange has allowed me to avoid binding errors caused by left over post data.
function changeUrl(selectionValue) {
var param = getParams();
param["dropdownselection"] = selectionValue;
window.location.replace(getBaseUrl() + buildQueryString(param));
}
//taken from http://javascript.about.com/library/blqs1.htm
function getParams() {
var qsParm = new Array();
var query = window.location.search.substring(1);
var parms = query.split('&');
for (var i = 0; i < parms.length; i++) {
var pos = parms[i].indexOf('=');
if (pos > 0) {
var key = parms[i].substring(0,pos);
var val = parms[i].substring(pos+1);
qsParm[key] = val;
}
}
return qsParm;
}
function getBaseUrl() {
var url = document.location.toString();
if (url.indexOf('?') != -1) {
url = url.substring(0, url.indexOf('?'));
}
return url;
}
function buildQueryString(param) {
var queryString = "?";
for (var key in param) {
queryString += key + "=" + param[key] + "&";
}
//remove last "&"
return queryString.substring(0,queryString.length - 1);
}

Resources