Is there a way to make custom lookup on dialog field on Microsoft Dynamics 365? - dialog

I have problems with my dialog field. I have button that opens dialog tab with field. It was supposed to show on that field lookup exact records(i guess i need select there by one field value). Right now i have this code:
DialogField Journal = dialog.addField(extendedTypeStr(JournalId));
This dialog line adds a field with all values on that EDT. I have 3 journal types - NEW, UPDATE, DELETE. Right now on that field lookup it shows me all 3 journal types. I want to make custom lookup that shows exact type , example - if i click that button on journal that has type "NEW", then it should show only "NEW" type of journal types on lookup. I heard there is something like dialog.addLookup or something. Can someone help me?

You already added your dialog field (in the dialog() method). Now add the dialogRunPost() method that is executed after the form GUI is initialized. At that point you can fetch the underlying FormStringControl behind the dialog field. Subscribing to the FormStringControl.OnLookup event allows you to override the lookup.
I did not have some journal data available, so I created a similar example with customers. My example dialog (MyDialog) takes a source customer (customerCaller) and shows a dialog with a custom lookup that only shows customers with the same customer group.
My example is also a standalone, runnable class and is not called from a form. Comments have been added to indicate where this affects the code.
Full example
public class MyDialog extends Runbase
{
// fields
protected Args args;
protected CustTable customerCaller;
protected DialogField dfCustomerId;
// construct
public static MyDialog newArgs(Args _args)
{
MyDialog ret = new MyDialog();
ret.args = _args;
return ret;
}
// initialize
public boolean init()
{
boolean ret = super();
// validate and fetch caller
if (args.record() && args.record().TableId == tableNum(CustTable))
//if (args.caller() && args.caller().dataset() == tableNum(CustTable)) --> when called from form
{
customerCaller = args.record();
//customerCaller = args.caller().record();
}
else
{
throw error(Error::missingRecord('My Dialog'));
}
return ret;
}
// build dialog
public Object dialog()
{
Dialog ret = super();
// optional reference to visualize the input
ret.addText('Caller customer group = ' + customerCaller.CustGroup);
// add field
dfCustomerId = ret.addField(extendedTypeStr(CustAccount)); // default lookup = all CustTable.AccountNum values
return ret;
}
public void dialogPostRun(DialogRunbase dialog)
{
super(dialog);
// subscribe to lookup event
FormStringControl fscCustomerId = dfCustomerId.control();
fscCustomerId .OnLookup += eventhandler(this.customerId_OnLookup);
}
// custom lookup for customer id
protected void customerId_OnLookup(FormControl _sender, FormControlEventArgs _e)
{
// cancel default
FormControlCancelableSuperEventArgs eventArgs = _e;
eventArgs.CancelSuperCall();
// define lookup query (list all customers with same customer group as input customer)
Query query = new Query();
QueryBuildDataSource qbds = SysQuery::findOrCreateDataSource(query, tableNum(CustTable));
SysQuery::findOrCreateRange(qbds, fieldNum(CustTable, CustGroup)).value(SysQuery::value(customerCaller.CustGroup));
// do lookup
SysTableLookup lookup = SysTableLookup::newParameters(tableNum(CustTable), _sender);
lookup.parmQuery(query);
lookup.addLookupfield(fieldNum(CustTable, AccountNum), true);
lookup.addLookupfield(fieldNum(CustTable, CustGroup));
lookup.performFormLookup();
}
// run dialog
public static void main(Args _args)
{
// I am running this dialog directly (not from a form), generating some random input
CustTable customer;
select firstonly customer where customer.CustGroup != '';
_args.record(customer);
// end of random input
MyDialog md = MyDialog::newArgs(_args);
md.init();
if (md.prompt())
{
md.run();
}
}
}
Result

Related

Add note to custom data record in code

I was searching for a solution to add a note to a database row I am creating in a custom table. I found the solution below from Ruslan for accessing the noteid, but I don't understand how this would be used to add a note to the row. I have all the code to create the row, I just need the attributes or function call to actually attach the text of the note to the row.
==================================================================
To have Note record automatically created when a new parent record gets saved, one should invoke the static PXNoteAttribute.GetNoteID(PXCache cache, object data) method when the parent record is inserted in the cache.
For example, to have Note record automatically created when a new Stock Item gets saved, you should subscribe to RowInserted handler for the InventoryItem DAC and call PXNoteAttribute.GetNoteID(...):
public class InventoryItemMaintExt : PXGraphExtension<InventoryItemMaint>
{
public void InventoryItem_RowInserted(PXCache sender, PXRowInsertedEventArgs e)
{
var noteCache = Base.Caches[typeof(Note)];
var oldDirty = noteCache.IsDirty;
PXNoteAttribute.GetNoteID<InventoryItem.noteID>(sender, e.Row);
noteCache.IsDirty = oldDirty;
}
}
The code snippet above can be incorporated into almost any custom BLC with a couple simple changes to replace InventoryItem with a custom DAC.
After implementing Gabriel's suggestions:
I do not seem to get any note in the database. The code compiles and runs fine, but the notes are not generated as far as I can tell. The note id is set in my table, but no data appears in the note table. Please take a look at my code and let me know what needs to change. Also, how do I get the note column into a grid, or does it automatically become available when it is done correctly?
Database field definition:
[NoteID] [uniqueidentifier] NULL
DAC field:
#region NoteID
public abstract class noteID : PX.Data.IBqlField
{
}
protected Guid? _NoteID;
[PXNote()]
public virtual Guid? NoteID
{
get
{
return this._NoteID;
}
set
{
this._NoteID = value;
}
}
#endregion
Code to create record:
private static bool acumaticaException(Exception e, EDImportExceptionMaint excpMaint, LingoRet850 res850)
{
excpMaint.Clear();
var except = new EDImportExcept();
except.ExceptReason = "U";
except.Active = true;
<...field assignments...>
except.OrderNbr = "";
PXNoteAttribute.SetNote(excpMaint.Exception.Cache, excpMaint.Exception.Current,
((PX.Data.PXOuterException)e).InnerMessages[0] + "-" + e.Message);
excpMaint.Exception.Insert(except);
excpMaint.Actions.PressSave();
return true;
}
To set the note of a record, use the SetNote()static function of PXNoteAttribute
PXNoteAttribute.SetNote(Base.Item.Cache, Base.Item.Current, "Hello, World!");
Calling SetNote will also take care of adding the Note record if it doesn't exist, so you don't have to call GetNoteID before setting the note value as in your question.
P.S. There is also a GetNote function which allows you to retrieve the current value of the note:
string note = PXNoteAttribute.GetNote(Base.Item.Cache, Base.Item.Current);

Loading popup value to InventoryID field

with code from Load value from popup to InventoryID field question am able to set the InventoyID from popup to the InventoryID field but the default behavior of Acumatica is missing, like after InventoryID is set from poup, am selecting Item Class then a pop up is firing and asking for confirmation, generally this happen only when we update Item Class for already created Item.
[PXButton]
[PXUIField(DisplayName = "Generate")]
public IEnumerable GenerateInv(PXAdapter adapter)
{
string inventoryCD = "ACUMATICA";
InventoryItem item = PXCache<InventoryItem>.CreateCopy(Base.Item.Current);
OrderedDictionary keys = new OrderedDictionary(StringComparer.OrdinalIgnoreCase)
{
{
typeof(InventoryItem.inventoryCD).Name,
adapter.View.Cache.GetValue(adapter.View.Cache.Current,
typeof(InventoryItem.inventoryCD).Name)
}
};
OrderedDictionary vals = new OrderedDictionary(StringComparer.OrdinalIgnoreCase)
{
{
typeof(InventoryItem.inventoryCD).Name, inventoryCD
}
};
adapter.View.Cache.Update(keys, vals);
if (adapter.Searches != null && adapter.Searches.Length > 0)
{
adapter.Searches[0] = inventoryCD;
}
return adapter.Get();
}
You can try to skip the message window by overriding the Field Verifying on the stock item page for InventoryItem.ItemClassID. You should use some condition to indicate you are running your customer process so the standard message will appear for normal use on the stock items page.
public virtual void InventoryItem_ItemClassID_FieldVerifying(PXCache cache, PXFieldVerifyingEventArgs e, PXFieldVerifying del)
{
try
{
del?.Invoke(cache, e);
}
catch (PXDialogRequiredException)
{
var someSkipMessageWindowCondition = true;
if (someSkipMessageWindowCondition)
{
return;
}
throw;
}
}
Because of the use of the private property doResetDefaultsOnItemClassChange in the Base graph, the process will function as if the message box was answered YES to default the field values from the new item class. If you do not want the values to change using the new class you will need to cancel the field defaultings for the fields found in InventoryItem_ItemClassID_FieldUpdated in the Base graph. This will point out which fields are being updated when doResetDefaultsOnItemClassChange == true.
A better answer to your question would be automatically answering NO which I am unsure how to do. Would be nice to know how do perform that function if someone else knows how to make that call.

How to dynamically add a content part in a Orchard content handler?

I have an Orchard content handler that calls
Filters.Add(new Orchard.ContentManagement.Handlers.ActivatingFilter<MyPart>("User"));
in its constructor to weld MyPart to a user content item.
How can i weld MyPart based on the content item id?
The issue here is that the content item is not yet created when the constructor is called. I tried hooking into the life cycle with overriding Activating() but that doesn't work either as the content item is also not created yet.
Okay, this task is really difficult. Here's my solution.
1) Create an extension method that welds a content part to a content item (sadly, we cannot use ContentItemBuild.Weld() as there's no chance to pass the content item)
// adopted from ContentItemBuilder.Weld<>()
public static TPart Weld<TPart>(this Orchard.ContentManagement.ContentItem aContentItem)
where TPart: Orchard.ContentManagement.ContentPart, new()
{
var partName = typeof(TPart).Name;
// obtain the type definition for the part
var typePartDefinition = aContentItem.TypeDefinition.Parts.FirstOrDefault(p => p.PartDefinition.Name == partName);
if (typePartDefinition == null) {
// If the content item's type definition does not define the part; use an empty type definition.
typePartDefinition = new Orchard.ContentManagement.MetaData.Models.ContentTypePartDefinition(
new Orchard.ContentManagement.MetaData.Models.ContentPartDefinition(partName),
new Orchard.ContentManagement.MetaData.Models.SettingsDictionary());
}
// build and weld the part
var part = new TPart { TypePartDefinition = typePartDefinition };
aContentItem.Weld(part);
return part;
}
2) Define a StorageFilter for dynamically welding the content part to the content item
public class BaseWeldBeforeStorageFilter<TPart, TRecord> : Orchard.ContentManagement.Handlers.IContentStorageFilter
where TPart: Orchard.ContentManagement.ContentPart, new()
where TRecord: Orchard.ContentManagement.Records.ContentPartRecord
{
// public
public BaseWeldBeforeStorageFilter(Orchard.Data.IRepository<TRecord> aPartRecords)
{
mPartRecords = aPartRecords;
}
...
public void Loading(Orchard.ContentManagement.Handlers.LoadContentContext aContext)
{
// dynamically weld TPart to content item when condition is met (is a user, does record exist)
if (aContext.ContentItem.Is<Orchard.Users.Models.UserPart>())
{
if (!aContext.ContentItem.Is<TPart>())
{
if (mPartRecords.Count(r => r.Id == aContext.ContentItem.Id) > 0)
aContext.ContentItem.Weld<TPart>();
}
}
}
...
// private
Orchard.Data.IRepository<TRecord> mPartRecords;
}
3) Define the content handler for the dynamic content part
public abstract class BasePartHandler<TPart, TRecord> : Orchard.ContentManagement.Handlers.ContentHandler
where TPart: Orchard.ContentManagement.ContentPart<TRecord>, new()
where TRecord: Orchard.ContentManagement.Records.ContentPartRecord, new()
{
// public
// the constructor of a content handler is called when a content item (e.g. user) is created
public BasePartHandler(Orchard.Data.IRepository<TRecord> aPartRecords)
{
...
// add storage filter for dynamically welding TPart to content item
Filters.Add(new BaseWeldBeforeStorageFilter<TPart, TRecord>(aPartRecords));
// enable storing TPart to associated table
Filters.Add(Orchard.ContentManagement.Handlers.StorageFilter.For<TRecord>(aPartRecords));
...
// listen to user creation, update, removal...
OnCreated<Orchard.Users.Models.UserPart>(UserCreated);
...
}
...
// private
private void UserCreated(Orchard.ContentManagement.Handlers.CreateContentContext aContext, Orchard.Users.Models.UserPart aUserPart)
{
if (...) // condition for checking whether user
CreatePartRecordWhenNeededAndWeldPart(aContext.ContentItem, ...);
}
private void CreatePartRecordWhenNeededAndWeldPart(Orchard.ContentManagement.ContentItem aContentItem)
{
TPart lPart = aContentItem.Weld<TPart>();
// assign record, adopted from StorageFilter.cs
// todo: find a way to do it the "Orchard way" as this feels like hack
lPart._record.Loader(r =>
new TRecord {
Id = aContentItem.Id,
ContentItemRecord = new Orchard.ContentManagement.Records.ContentItemRecord {Id = aContentItem.Id}
});
// there are situations where part record already exists in DB but part is not welded at this point, thus check for existing record to avoid
// - creating record multiple times
// - NHibernate exception
if (!mPartRecords.Table.Contains(lPart.Record))
mPartRecords.Create(lPart.Record);
}
private Orchard.Data.IRepository<TRecord> mPartRecords;
}
As for now, the dynamic content part handling is working but I'm still unsure how to create a content part record in Orchard properly (see todo hint in source code of step 3).

How to ticker List elements when they are Commands?

If I create a List based on an array of Commands, and the text of some Commands are not entirely shown in the List, although the List preferredWidth is set to the Form preferredWidth, how to ticker them ?
Thank you very much
Add the below class in your midlet class or create a new class file for that:
class TickerRenderer extends DefaultListCellRenderer {
private DefaultListCellRenderer selectedRenderer = new DefaultListCellRenderer(false);
private List parentList;
public TickerRenderer() {
super(false);
}
public boolean animate() {
if (parentList != null && parentList.getComponentForm() != null) {
if (selectedRenderer.isTickerRunning()) {
if (selectedRenderer.animate()) {
parentList.repaint();
}
}
}
return super.animate();
}
public Component getListCellRendererComponent(List list, Object value, int index, boolean isSelected) {
if (isSelected) {
selectedRenderer.getListCellRendererComponent(list, value, index, isSelected);
// sometimes the list asks for a dummy selected value for size calculations and this might
// break the tickering state
if (index == list.getSelectedIndex()) {
if (selectedRenderer.shouldTickerStart()) {
if (!selectedRenderer.isTickerRunning()) {
parentList = list;
list.getComponentForm().registerAnimated(this);
selectedRenderer.startTicker(UIManager.getInstance().getLookAndFeel().getTickerSpeed(), true);
}
} else {
if (selectedRenderer.isTickerRunning()) {
selectedRenderer.stopTicker();
}
}
}
return selectedRenderer;
} else {
return super.getListCellRendererComponent(list, value, index, isSelected);
}
}
}
Use it like this:
List cmdList = new List(cmds);
cmdList.setListCellRenderer(new TickerRenderer());
Try this code, it will show list in dialog box on clicking "Show list" command and will also enable ticker initially. Below is the code which shows how to use the above mentioned class to see ticker in list when list is contained in dialog.
Don't forget to make your list final so that it can be used in inner classes.
form.addCommand(new Command("Show list") { // add command in form and override its actionPerformed method
public void actionPerformed(ActionEvent evt) {
Dialog d = new Dialog() { // create an instance of dialog and make it an inner class so that you can override onShow() method and set focus on list when dialog gets initialized and also can set its index to ur preferred one (here it's 0)
protected void onShow() { // overriding of onShow() method
list.requestFocus(); // set focus on list
list.setSelectedIndex(0); // set selected index to 0
}
};
d.addComponent(list); // add list in dialog
d.show(); // show dialog
}
});
This code shows my list in dialog and starts ticker initially. If it doesn't help, post your code, i will try to see it.

filter look up in dialog

I Have created a dialog in a class, the dialog method is as below
static void dialog(Args _args)
{
Dialog dialog;
DialogField dialogFieldCurrentState;
DialogField dialogFieldNewState;
CustInvoiceTable custInvoiceTable;
;
custInvoiceTable = _args.record();
dialog = new Dialog("Change State");
dialogFieldCurrentState = dialog.addField(TypeID(State_LT),"Current State: ");
dialogFieldCurrentState.value(custInvoiceTable.State);
dialogFieldCurrentState.enabled(false);
dialogFieldNewState = dialog.addField(TypeID(State_LT),"New State: ");
if (dialog.run())
{
custInvoiceTable.State = dialogFieldNewState.value();
}
}
in my dialog there are two fileds Current State and New State .Now when i select the New State the list of all
states is displayed(irrespective of country) which i dont want. Only the states respective of country has to be shown
in the lookup
. I need to make use of a filter something like e.g. while select while select AddressState
where addressState.CountryRegionId == custInvoiceTable.CountryRegionId; so that only states which
are related to a country is shown.
State_LT here is an string EDT (where i put in the relation of State_LT) State_LT == AddressState.StateId
IN AdressState there is a method lookupStateId(), How to call it from a dialog(code above)
?
I am answering to your last question: "IN AdressState THERE IS A METHOD lookupStateId(), HOW TO CALL IT FROM A DIALOG(code above) ?" - by the way writing in capital letters doesn't help people understand your point better.
It is not clear why your dialog is a static method, anyway you'd need the following.
Let's say your ClassDeclaration looks something like this:
class TestClass1 extends RunBase
{
Dialog dialog;
DialogField dialogFieldCurrentState;
DialogField dialogFieldNewState;
// etcetera
}
Your dialog is something like this:
public Object dialog()
{
;
dialog = super();
dialogFieldCurrentState = dialog.addField(TypeID(AddressStateId),"Current State: ");
dialogFieldCurrentState.enabled(false);
dialogFieldNewState = dialog.addField(TypeID(AddressStateId),"New State: ");
dialogFieldNewState.lookupButton(FormLookupButton::Always); // If needed
return dialog;
}
To implement a lookup the way you want it you need to do two things. First, open the dialog, right click on the New State, click Setup, and check the control's System Name. If for example it is Fld2_1 then you need to create the following method:
void fld2_1_lookup()
{
Object control = dialog.formRun().controlCallingMethod();
;
AddressState::lookupStateId(control, dialogFieldNewState.value());
}
Second, it is necessary to override the following method:
public void dialogPostRun(DialogRunbase _dialog)
{
super(_dialog);
_dialog.dialogForm().formRun().controlMethodOverload(true);
_dialog.dialogForm().formRun().controlMethodOverloadObject(this);
}
That should do the trick. I haven't done it for a while but I don't think I forgot something.
Example of looking up customer in dialog:
For example, to have a customer choice dropdown in the dialog,
In report class declaration method --->
DialogField CustomerDlg;
CustAccount customer;
In the reports dialog method: ----->
dialog.addGroup("Customer");
CustomerDlg = dialog.addField(typeid(CustAccount));
CustomerDlg.value(customer);
In the getFromDialog method: ---->
...
customer = CustomerDlg.value();

Resources