I want to show a popup with some message to user whenever screen gets loaded for the first time for each record.
For example when Sales Order screen is loaded for particular sales order, it should show the popup only once. Then user navigates to a next sales order, it should again show the popup for that particular sales order only once.
I have written the code in constructor and RowSelected event, but it does not has the Current record. That is CRCurrentCaseNotes is always null in both this events. However, with the button (ViewNotes in below code sample), it works.
[PXViewName("CRCurrentCaseNotes")]
[PXCopyPasteHiddenView]
public PXSelect<Note,
Where<CRCase.caseID, Equal<Current<CRCase.caseID>>>> CRCurrentCaseNotes;
public CRCaseMaintExtension()
: base()
{
if (CRCurrentCaseNotes.Current != null)
{
CRCurrentCaseNotes.AskExt();
}
}
protected virtual void CRCase_RowSelecting(PXCache cache, PXRowSelectingEventArgs e)
{
var caseRow = (CRCase)e.Row;
if (caseRow == null) return;
if (CRCurrentCaseNotes.Current != null)
{
CRCurrentCaseNotes.AskExt();
}
}
// SAME CODE WORKS WITH THE BUTTON CLICK
public PXAction<CRCase> viewNotes;
[PXUIField(DisplayName = "View Notes")]
[PXButton]
protected virtual IEnumerable ViewNotes(PXAdapter adapter)
{
if (CRCurrentCaseNotes.Current != null)
{
CRCurrentCaseNotes.AskExt();
}
return adapter.Get();
}
Try to show the dialog when the primary DAC key field is modified:
protected void CRCase_CaseID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
CRCase case = e.Row as CRCase;
if (case != null && case.CaseID != null && !e.ExternalCall)
{
// Show dialog
}
}
If there's no other way you could use JavaScript to show/hide the SmartPanel:
document.getElementById("<%=SmartPanelID.ClientID %>").style.display = 'block';
EDIT:
Dialog can only be shown from an action event handler (FieldUpdated won't work) or from JavaScript. To open dialog when the page open, you can try hooking DocumentReady event in JavaScript and call the Acumatica action from JavaScript too: px_alls['ds'].executeCallback('ActionName');
Related
I need to change the default value of the Residential Delivery checkbox on the Customers AR.30.30.00 screen (Shipping tab) to checked by default. See screenshot:
In 2017R2, this event handler worked without error:
public class CustomerMaint_Extension : PXGraphExtension<CustomerMaint>
{
protected virtual void LocationExtAddress_CResedential_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
var row = (LocationExtAddress)e.Row;
if (row != null)
{
e.NewValue = true; // checked by default
}
}
}
I'm updating this customization for 2020R2. It appears that LocationExtAddress has been replaced with DefLocationExt in newer versions. (Resedential is mis-spelled intentionally in the code... that's how Acumatica defined it.) I've tried changing the event handler to:
protected virtual void DefLocationExt_CResedential_FieldDefaulting(PXCache sender, PXFieldDefaultingEventArgs e)
{
var row = (DefLocationExt)e.Row;
if (row != null)
{
e.NewValue = true; // checked by default
}
}
But this results in a run-time error:
Failed to subscribe the event PX.Objects.AR.CustomerMaint_Extension::DefLocationExt_CResedential_FieldDefaulting in the graph PX.Objects.AR.CustomerMaint. The method signature looks like an event handler, but the cache DefLocationExt has not been found in the list of auto-initialized caches. Remove unused event handlers from the code.
How can I attach an event to this field in 2020R2?
Try a generic event handler and see if you get the same result.
It might look something like this.
protected virtual void _(Events.FieldDefaulting<PX.Objects.CR.Standalone.Location.cResedential> e)
{
var row = (PX.Objects.CR.Standalone.Location)e.Row;
if (row != null)
{
e.NewValue = true; // checked by default
}
}
I'm trying to store results from a BQL statement in a list by looping through and adding the objects to the list during a PXAction, so I can run some checks against the list later in a FieldUpdated event. However When I get to the FieldUpdated event, the contents of the list are back to zero.
public class SOShipmentEntry_Extension : PXGraphExtension<SOShipmentEntry>
{
public PXSelectJoin<INItemLotSerial, InnerJoin<SOShipLine, On<INItemLotSerial.inventoryID, Equal<Current<SOShipLine.inventoryID>>>>, Where<SOShipLine.shipmentNbr, Equal<Current<SOShipment.shipmentNbr>>>> AvailSerialList;
List<INItemLotSerial> AvailToPick = new List<INItemLotSerial>();
Here's where I add results into the list (added a watch on debugger to make sure I have a count of the content added, and verify that the list has content)
public PXAction<SOShipment> uploadlotserial;
[PXUIField(DisplayName = "Upload LotSerial", MapEnableRights = PXCacheRights.Select, MapViewRights = PXCacheRights.Select)]
[PXButton]
public virtual IEnumerable uploadLotserial(PXAdapter adapter)
{
if (AvailToPick.Count == 0)
{
foreach (INItemLotSerial pick in AvailSerialList.Select(this))
{
AvailToPick.Add(pick);
}
}
if (SerialFilter.AskExt() == WebDialogResult.OK)
{
GenerateSoShipmentSplitLine();
DeleteAllTempRecords();
}
DeleteAllTempRecords();
SerialList.Cache.Clear();
SerialList.Cache.ClearQueryCache();
return adapter.Get();
}
However when I hit this event, the count shows zero.
protected void InfoLotSerialFilter_LotSerialNbr_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
if (InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (InfoLotSerialFilter)e.Row;
if (row.LotSerialNbr != null)
{
if (AvailToPick.Count > 0)
{
Any insight as to why the list contents are cleared would be appreciated. Thank you!
I would suggest that instead of the list you create a simple View for INItemLotSerial that will create a cache when the page is run. You can Insert records into the cache during your action and then push them into a List<> and count them during your event.
I need to run some address validation on Customer Location addresses using a 3rd party API to determine if the address is residential or commercial. This validation should run whenever an address field is changed. In other words, the validation should be run in the Address_RowUpdated event handler.
Because the function is calling a 3rd party API, I believe that it should be done in a separate thread, using PXLongOperation so that it does not hold up address saving and fails gracefully if the API is unavailable or returns an error.
However, I am not sure if the architecture of running a long operation within an event handler is supported or if a different approach would be better.
Here is my code.
public class CustomerLocationMaint_Extension : PXGraphExtension<CustomerLocationMaint>
{
protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
if (row != null)
{
Location location = this.Base.Location.Current;
PXCache locationCache = Base.LocationCurrent.Cache;
PXLongOperation.StartOperation(Base, delegate
{
RunCheckResidential(location, locationCache);
});
this.Base.LocationCurrent.Cache.IsDirty = true;
}
}
protected void RunCheckResidential(Location location, PXCache locationCache)
{
string messages = "";
PX.Objects.CR.Address defAddress = PXSelect<PX.Objects.CR.Address,
Where<PX.Objects.CR.Address.addressID, Equal<Required<Location.defAddressID>>>>.Select(Base, location.DefAddressID);
FValidator validator = new FValidator();
AddressValidationReply reply = validator.Validate(defAddress);
AddressValidationResult result = reply.AddressResults[0];
bool isResidential = location.CResedential ?? false;
if (result.Classification == FClassificationType.RESIDENTIAL)
{
isResidential = true;
} else if (result.Classification == FClassificationType.BUSINESS)
{
isResidential = false;
} else
{
messages += "Residential classification is: " + result.Classification + "\r\n";
}
location.CResedential = isResidential;
locationCache.Update(location);
Base.LocationCurrent.Update(location);
Base.Actions.PressSave();
// Display relevant messages
if (reply.HighestSeverity == NotificationSeverityType.SUCCESS)
String addressCorrection = validator.AddressCompare(result.EffectiveAddress, defAddress);
if (!string.IsNullOrEmpty(addressCorrection))
messages += addressCorrection;
}
PXSetPropertyException message = new PXSetPropertyException(messages, PXErrorLevel.Warning);
PXLongOperation.SetCustomInfo(new LocationMessageDisplay(message));
//throw new PXOperationCompletedException(messages); // Shows message if you hover over the success checkmark, but you have to hover to see it so not ideal
}
public class LocationMessageDisplay : IPXCustomInfo
{
public void Complete(PXLongRunStatus status, PXGraph graph)
{
if (status == PXLongRunStatus.Completed && graph is CustomerLocationMaint)
{
((CustomerLocationMaint)graph).RowSelected.AddHandler<Location>((sender, e) =>
{
Location location = e.Row as Location;
if (location != null)
{
sender.RaiseExceptionHandling<Location.cResedential>(location, location.CResedential, _message);
}
});
}
}
private PXSetPropertyException _message;
public LocationMessageDisplay(PXSetPropertyException message)
{
_message = message;
}
}
}
UPDATE - New Approach
As suggested, this code now calls the LongOperation within the Persist method.
protected virtual void Address_RowUpdated(PXCache sender, PXRowUpdatedEventArgs e)
{
PX.Objects.CR.Address row = (PX.Objects.CR.Address)e.Row;
if (row != null)
{
Location location = Base.Location.Current;
LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
locationExt.UsrResidentialValidated = false;
Base.LocationCurrent.Cache.IsDirty = true;
}
}
public delegate void PersistDelegate();
[PXOverride]
public virtual void Persist(PersistDelegate baseMethod)
{
baseMethod();
var location = Base.Location.Current;
PXCache locationCache = Base.LocationCurrent.Cache;
LocationExt locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
if (locationExt.UsrResidentialValidated == false)
{
PXLongOperation.StartOperation(Base, delegate
{
CheckResidential(location);
});
}
}
public void CheckResidential(Location location)
{
CustomerLocationMaint graph = PXGraph.CreateInstance<CustomerLocationMaint>();
graph.Clear();
graph.Location.Current = location;
LocationExt locationExt = location.GetExtension<LocationExt>();
locationExt.UsrResidentialValidated = true;
try
{
// Residential code using API (this will change the value of the location.CResedential field)
} catch (Exception e)
{
throw new PXOperationCompletedWithErrorException(e.Message);
}
graph.Location.Update(location);
graph.Persist();
}
PXLongOperation is meant to be used in the context of a PXAction callback. This is typically initiated by a menu item or button control, including built-in actions like Save.
It is an anti-pattern to use it anytime a value changes in the web page. It should be used only when a value is persisted (by Save action) or by another PXAction event handler. You should handle long running validation when user clicks on a button or menu item not when he changes the value.
For example, the built in Validate Address feature is run only when the user clicks on the Validate Address button and if validated requests are required it is also run in a Persist event called in the context of the Save action to cancel saving if validation fails.
This is done to ensure user expectation that a simple change in a form/grid value field doesn't incur a long validation wait time that would lead the user to believe the web page is unresponsive. When the user clicks on Save or a specific Action button it is deemed more reasonable to expect a longer wait time.
That being said, it is not recommended but possible to wrap your PXLongOperation call in a dummy Action and asynchronously click on the invisible Action button to get the long operation running in the proper context from any event handler (except Initialize):
using PX.Data;
using System.Collections;
namespace PX.Objects.SO
{
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public PXAction<SOOrder> TestLongOperation;
[PXUIField(DisplayName = "Test Long Operation", Visible = false, Visibility = PXUIVisibility.Invisible)]
[PXButton]
public virtual IEnumerable testLongOperation(PXAdapter adapter)
{
PXLongOperation.StartOperation(Base, delegate ()
{
System.Threading.Thread.Sleep(2000);
Base.Document.Ask("Operation Done", MessageButtons.OK);
});
return adapter.Get();
}
public void SOOrder_OrderDesc_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
if (!PXLongOperation.Exists(Base.UID))
{
// Calling Action Button asynchronously so it can run in the context of a PXAction callback
Base.Actions["TestLongOperation"].PressButton();
}
}
}
}
I have a usercontrol that I want to have the grid inside so I don't have to duplicate that grid on every page. Except when I sort, page, or anything that does a post back the usercontrol reloads and loses its datasource. My plan is to retrieve the search criteria from the parent page(since it already has it from the criteria controls). That way when the NeedDataSource is called it still has the criteria to pass back the right results.
How do I get where you see SuperSearch to be whichever page might be the parent like StateToState.
public SearchCriteria SearchCriteria
{
get
{
Page parent = this.Page;
if (parent != null)
{
var superSearch = parent as SuperSearch;
if (superSearch != null) return superSearch.SearchCriteria;
}
return new SearchCriteria();
}
}
Create an event handler 'event EventHandler NeedSearchCriteria' on your usercontrol that gets fired on your parent page
On your aspx page:
<UC:Grid runat="server" ID="ucGrid" OnNeedSearchCriteria="ucGrid_OnNeedSearchCriteria" />
In the code behind:
public void ucGrid_OnNeedSearchCriteria(object sender, EventArgs e)
{
ucGrid.Criteria = Criteria;
}
And on the usercontrol code behind:
public event EventHandler NeedSearchCriteria;
private SearchCriteria _criteria;
public SearchCriteria Criteria
{
get
{
if (_criteria == null && NeedSearchCriteria != null)
{
NeedSearchCriteria(this, new EventArgs());
}
return _criteria ?? new SearchCriteria();
}
set
{
_criteria = value;
}
}
Is there any way to prevent the change of a tab in TabControl in Silverlight 4?
A simple case is when I've got a form with some data, and I want to ask the user if he/she wants to save this data before actually changing the tab.
I've seen code examples on how to do this in WPF, but not in Silverlight.
What can I do to stop the tab from changing?
Bind SelectedIndex to a property on your data context.
<sdk:TabControl SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}">
<sdk:TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</sdk:TabItem>
<sdk:TabItem Header="TabItem">
<Grid Background="#FFE5E5E5"/>
</sdk:TabItem>
</sdk:TabControl>
In the SET accessor, write your code to check whether the user really wants to do what they're trying to do.
public class Context : INotifyPropertyChanged
{
int _SelectedIndex = 0;
public int SelectedIndex
{
get
{
return _SelectedIndex;
}
set
{
MessageBoxResult result = MessageBox.Show("Do you want to save?", "Really?", MessageBoxButton.OKCancel);
if (result == MessageBoxResult.OK)
{
_SelectedIndex = value;
}
RaisePropertyChanged("SelectedIndex");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
Net effect is, if the user chooses 'cancel' on the dialogue the private variable is never changed - the PropertyChanged event fires, rebinding the selected index to the existing value.
Hope this is what you were trying to accomplish.
UPDATE (11/10/2012) - Alternate method (possibly for SL5?). Write code behind to tie into SelectionChanged event of your TabControl, reset the tab control's selected item property based on your test.
private void TabControl_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0)
{
MessageBoxResult result = MessageBox.Show("Do you want to save?", "Really?", MessageBoxButton.OKCancel);
if (result != MessageBoxResult.OK)
{
((TabControl)sender).SelectionChanged -= new SelectionChangedEventHandler(TabControl_SelectionChanged);
((TabControl)sender).SelectedItem = e.RemovedItems[0];
((TabControl)sender).SelectionChanged += new SelectionChangedEventHandler(TabControl_SelectionChanged);
}
}
}