I need to get all controls from a tab on my form, I tried to use this option I found online but it didn't work ideas anyone?
var tabs = Xrm.Page.ui.tabs.get();
var fieldList = new Array();
for (var i in tabs)
{
var tab = tabs[i];
if(tab.getName() == "tab_2")
{
tab.sections.forEach(function (section, sectionIndex)
{
section.controls.forEach(function (control, controlIndex)
{
switch (control.getControlType())
{
case "standard":
case "lookup":
case "optionset":
var attribute = control.getAttribute();
if (attribute != null)
{
fieldList.push(attribute.getName());
}
break;
}
});
});
}
}
}
I just tried the code and it works.
It looks for a tab called tab_2 and then gets all controls within that tab and adds the control.name to an array (fieldList)
The result is a simple array of control names. When I ran this on my Contact form (with the Tab Name adjusted) I got the following results:
[
"fullname",
"nickname",
"employeeid",
"jobtitle",
"parentcustomerid",
"emailaddress1",
"telephone2",
"telephone1",
"mobilephone",
"fax",
"address1_composite",
"preferredsystemuserid",
"familystatuscode",
"spousesname",
"birthdate",
"description",
"ownerid",
"createdon"
]
If this is not working for you, I suggest the following:
Check the name of the CRM Tab
Depending on where you are running this (e.g. in a web resource or developer console) you may need to change the code context
Related
Is there a way to hide some Functions/Operators in the Row Filter of APEX 19.1's Interactive Report? Some end-users get confused with as many functions/operators that they do not used.
Thanks for any considerations.
While APEX doesn't support this out of the box, it is doable with JavaScript. Every time the filter dialog is displayed, content is brought from the server to the client and injected into the DOM. You just need to modify the content before the user can see it. One way to achieve this is by using the MutationObserver interface: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Here are some steps you could follow to do it (tested in APEX 19.2):
Go to the Interactive Report and set the Static ID to my-irr.
Go to the page level attributes and add the following code to the Function and Global Variable Declaration field:
function removeIRFilterOperators() {
var irRegId = 'my-irr';
var filterOperatorsToRemove = ['!=', 'ABS'];
var observer;
function detectFilterDialog(mutationsList) {
for (var mIdx = 0; mIdx < mutationsList.length; mIdx++) {
if (mutationsList[mIdx].addedNodes.length &&
mutationsList[mIdx].addedNodes[0].classList &&
mutationsList[mIdx].addedNodes[0].classList.contains('a-IRR-dialog--filter')) {
removeOperators();
}
}
}
function removeOperators() {
var anchors = document.querySelectorAll('#' + irRegId + '_row_filter_operators a');
for (var aIdx = 0; aIdx < anchors.length; aIdx++) {
if (filterOperatorsToRemove.includes(anchors[aIdx].textContent)) {
anchors[aIdx].parentElement.parentElement.removeChild(anchors[aIdx].parentElement);
}
}
}
observer = new MutationObserver(detectFilterDialog);
observer.observe(
document,
{
attributes: false,
childList: true,
subtree: true
}
);
}
removeIRFilterOperators();
The MutationObverver uses the detectFilterDialog function to detect when the filter dialog is added to the DOM. When that happens, the removeOperators function removes the specified options from the operator's list. All you need to do is update the filterOperatorsToRemove array to include the list of operators you want to remove.
If you're talking about the "Actions" menu, then yes - go to IR's attributes and enable/disable any option you want:
I am using an adaptive card (1.0) & am trying to give a hyperlink for the user to click so that he/she can navigate to a particular ServiceNow ticket.
I am not able to figure out how the dynamic sys_id can be passed to the hyperlink as the URI/URN.
The hyperlink for the ticket changes for each ticket so it has to be dynamic. But I am not seeing an option to make the URI/URN dynamic.
I have tried escaping & separating the numbers from texts.
new AdaptiveFact()
{
Title="More Information",
Value ="[Click here to open the ticket in SNOW](https://URL.service-now.com/)"
}
I should be able to take sys_id as a parameter(URI/URN) to navigate to the exact incident in SNOW.
The complete Card generator code is as below
AdaptiveCard card = new AdaptiveCard("1.0");
card.Body.Add(new AdaptiveContainer()
{
Items = new List<AdaptiveElement>()
{
new AdaptiveTextBlock()
{
Text= $"Ticket Status for {JSONArray.1stElementname.ToUpper()} is as follows.",
Weight= AdaptiveTextWeight.Bolder,
Size= AdaptiveTextSize.Large
},
new AdaptiveColumnSet()
{
Columns = new List<AdaptiveColumn>()
{
new AdaptiveColumn()
{
Items = new List<AdaptiveElement>()
{
new AdaptiveFactSet()
{
Facts = new List<AdaptiveFact>()
{
new AdaptiveFact()
{
Title="Short Description",
Value=JSONArray.2ndElementname
},
new AdaptiveFact()
{
Title="More Information",
Value ="[Click here to open the ticket in SNOW](https://service-now.com/JSONArray.3rdDElementname)"
}
},
Separator = true
}
}
}
}
}
},
});
Attachment attachment = new Attachment()
{
ContentType = AdaptiveCard.ContentType,
Content = card
};
return attachment;
It looks like you need to insert the data from the JSON object into your URL. Right now "JSONArray.3rdDElementname" is just rendering as part of the URL instead of pulling the value from the object. Try using string interpolation.
Value = $"[Click here to open the ticket in SNOW](https://service-now.com/{JSONArray.3rdDElementname})"
Here is more documentation on how to interpolation data into a string.
Glad this helped.
I am trying to load an entity based on a Query and allow the user to edit it. The entity loads without issues from the query, however it does not load its related entities, leaving detail pickers unfilled when loading the edit screen.
This is the code that I have:
myapp.BrowseCOAMissingHoldingCompanies.VW_ChartOfAccountsWithMissingHoldingCompanies_ItemTap_execute = function (screen) {
var accountName = screen.VW_ChartOfAccountsWithMissingHoldingCompanies.selectedItem.AccountFullName;
return myapp.activeDataWorkspace.Accounting360Data.FindChartOfAccountsMappingByAccountName(accountName)
.execute().then(function (query) {
var coa = query.results[0];
return myapp.showAddEditChartOfAccountsMapping(coa, {
beforeShown: function (addEditScreen) {
addEditScreen.ChartOfAccountsMapping = coa;
},
afterClosed: function () {
screen.VW_ChartOfAccountsWithMissingHoldingCompanies.refresh();
}
});
});
};
Interestingly if I open the browse screen (and nothing else) of that entity type first (which does retrieve the entity), then the related entities load correctly and everything works, but I can't figure out how to make that level of load happen in this code.
One method of tackling this (and to avoid the extra query execution of a follow on refresh) is to use the expand method to include any additional navigation properties as follows:
myapp.BrowseCOAMissingHoldingCompanies.VW_ChartOfAccountsWithMissingHoldingCompanies_ItemTap_execute = function (screen) {
var accountName = screen.VW_ChartOfAccountsWithMissingHoldingCompanies.selectedItem.AccountFullName;
return myapp.activeDataWorkspace.Accounting360Data.FindChartOfAccountsMappingByAccountName(
accountName
).expand(
"RelatedEntity," +
"AnotherRelatedEntity," +
"AnotherRelatedEntity/SubEntity"
).execute().then(function (query) {
var coa = query.results[0];
return myapp.showAddEditChartOfAccountsMapping(coa, {
beforeShown: function (addEditScreen) {
addEditScreen.ChartOfAccountsMapping = coa;
},
afterClosed: function () {
screen.VW_ChartOfAccountsWithMissingHoldingCompanies.refresh();
}
});
});
}
As you've not mentioned the name of your entity's navigational properties, I've used coa.RelatedEntity, coa.AnotherRelatedEntity and coa.AnotherRelatedEntity.SubEntity in the above example.
As covered by LightSwitch's intellisense (in msls-?.?.?-vsdoc.js) this method 'Expands results by including additional navigation properties using an expression defined by the OData $expand system query option' and it accepts a single parameter of 'An OData expand expression (a comma-separated list of names of navigation properties)'.
The reason your forced refresh of coa also populates the navigational properties is that LightSwitch's refresh method implicitly expands all navigation properties (provided you don't specify the navigationPropertyNames parameter when calling the refresh). The following shows the internal implementation of the LightSwitch refresh method (with the implicit expand behaviour executing if the navigationPropertyNames parameter is null):
function refresh(navigationPropertyNames) {
var details = this,
properties = details.properties.all(),
i, l = properties.length,
property,
propertyEntry,
query;
if (details.entityState !== _EntityState.unchanged) {
return WinJS.Promise.as();
}
if (!navigationPropertyNames) {
navigationPropertyNames = [];
for (i = 0; i < l; i++) {
property = properties[i];
propertyEntry = property._entry;
if (isReferenceNavigationProperty(propertyEntry) &&
!isVirtualNavigationProperty(propertyEntry)) {
navigationPropertyNames.push(propertyEntry.serviceName);
}
}
}
query = new _DataServiceQuery(
{
_entitySet: details.entitySet
},
details._.__metadata.uri);
if (navigationPropertyNames.length > 0) {
query = query.expand(navigationPropertyNames.join(","));
}
return query.merge(msls.MergeOption.unchangedOnly).execute();
}
However, if you take the refresh approach, you'll be performing an additional unnecessary query operation.
Entity Framework uses lazy loading by default, so related data will be loaded on demand, but in your case that's too late because the entity is already client-side a that point.
Try using the Include method in your query if you want eager loading.
Calling refresh on the details of the entity seems to do it:
return coa.details.refresh().then(function() {
return myapp.showAddEditChartOfAccountsMapping(coa, {
beforeShown: function (addEditScreen) {
addEditScreen.ChartOfAccountsMapping = coa;
},
afterClosed: function () {
screen.VW_ChartOfAccountsWithMissingHoldingCompanies.refresh();
}
});
});
You should use load method to fetch related data from Server. At this time we don't have any ways to force msls load related data.
I'm using a marionette compositeview to display a collection, the code is below.
However, inside my collection I have 5 filters that are bound to events that then update the collection from the API.
The way I see it, theres two options, I would like opinions on which is better:
1) Use a layout view, somehow figure out how a compositeview can catch the filter views options and update the collection.
2) Use onRender to display the filter views and again catch the events in the compositeview
define(["marionette", "text!app/templates/posts/collection.html", "app/collections/posts", "app/views/posts/item"],
function(Marionette, Template, Collection, Item) {
"use strict"
return Backbone.Marionette.CompositeView.extend({
template: Template,
itemView: Item,
itemViewContainer: "tbody",
filter: {
from: 0,
to: 15,
publish_target: null,
status: null,
type: null,
publish_from_date: null, //publish_from_date=2014-01-07
publish_to_date: null, //publish_to_date=2014-01-07
publish_from_time: null, //publish_from_time=01%3A00%20AM
publish_to_time: null, //publish_to_time=12%3A30%20AM
location_id: null,
client_id: null
},
events: {
'change .filterBy': 'onClickFilter',
'change .filterByDate': 'onClickFilterDate'
},
collectionEvents: {
'sync': 'hideLoading'
},
initialize: function(options) {
//set loading, important we do this because we re-trigger the collection
this.setLoading();
// don't call a new collection unless its the init load, we lose collection automatically triggered events otherwise
if (_.isEmpty(options) || !_.has(options, 'newCollection')) {
this.collection = new Collection()
}
//strip any null key values from this.filter so the api doesnt filter crap
this.filter = _.cleanNullFieldsFromObject(this.filter);
//fetch the collection
return this.collection.fetch({data: this.filter})
},
// date was triggered, so get the details
onClickFilterDate: function() {
var publishFrom = new Date($('#publish_from_date').val());
var publishTo = new Date($('#publish_to_date').val());
this.filter.publish_from_date = _.dateToYMD(publishFrom);
this.filter.publish_to_date = _.dateToYMD(publishTo);
this.filter.publish_from_time = _.dateToHM(publishFrom);
this.filter.publish_to_time = _.dateToHM(publishTo);
// from time is greater than two time, then fetch the collection
if ( (publishFrom.getTime() / 1000) < (publishTo.getTime() / 1000) ) {
this.initialize({newCollection: true});
}
},
// a typical filter is clicked, so figure out whats happening
onClickFilter: function (ev) {
var type = $('#'+ev.currentTarget.id).data('type')
switch (type) {
case 'status':
this.filter.status = $('#filterStatus').val();
break;
case 'publish_target':
this.filter.publish_target = $('#filterPublishTarget').val();
break;
case 'type':
this.filter.type = $('#filterType').val();
break;
case 'client_id':
this.filter.client_id = $('#filterClientId').val();
break;
case 'location_id':
this.filter.location_id = $('#filterLocationId').val();
break;
}
this.initialize({newCollection: true});
},
hideLoading: function() {
this.$el.find('.loading-latch').removeClass('loading-active');
},
//set loading by appending to the latch
setLoading: function() {
this.$el.find('.loading-latch').addClass('loading-active');
}
})
})
define(["marionette", "text!app/templates/posts/item.html"],
function(Marionette, Template) {
"use strict"
return Backbone.Marionette.ItemView.extend({
template: Template,
tagName: "tr",
initialize: function () {
this.model.set('statusReadable', this.model.getStatus());
}
})
})
I actually went ahead and built both. I prefer the layout view.
Both have the concept of having a filter object that fetches and passes query params to the api.
Here are both solutions.
Using a layout that catches filter:changes vents to then update a filter object that gets passed into the collection view (I favour this because only the collection gets redrawn when a filter is changed)
http://pastebin.com/XNmQjs1i
Using a collection view that catches class events and redraws the view (I don't favour this because everything gets redrawn everytime the user changes a filter)
http://pastebin.com/WML2iiM4
I'm sure this might come in handy for marionette newbies, theres a lot of my learning poured into this. Enjoy.
I have found plenty of posts about this, but none of them are solving my issue. My code right now:
#Html.ListBox("SelectedNewsletter", Model.Newsletters)
and
public MultiSelectList Newsletters
{
get
{
return new MultiSelectList(
new[]
{
// TODO: Fetch from your repository
new { Id = 1, Name = "item 1" },
new { Id = 2, Name = "item 2" },
new { Id = 3, Name = "item 3" },
},
"Id",
"Name"
);
// return new MultiSelectList(PromoNewsletter.All, "IdString", "Display");
}
}
As far as I can see, it's all hard coded now, and it still gives the same error. I want to do a ListboxFor, but I am trying to just get a listbox to work. I have replaced my int id with a string representation, based on advice I found elsewhere, but now I don't see what else I can do. It just plain is not working, even with all hard coded values and not binding to a property on my ViewModel. Where am I going wrong ?
The error is occurring because you have a property in the Model or ViewData/ViewBag with the name SelectedNewsletter.
Give a different name for the ListBox if you can't rename that property.
UPDATE:
After little more experimenting I figured out that the problem is you may be setting an integer value to the SelectedNewsletter that is somewhere in the ViewData/ViewBag or Model.
You can set the values that has to be selected in the ListBox as a string or array of strings to the SelectedNewsletter.
i.e SelectedNewsletter = "1";
or
SelectedNewsletter = new[] { "1", "3" };
You can also use strongly typed helper to make things easy,
#Html.ListBoxFor(m => m.SelectedNewsletter, Model.NewsLetters);