Reputation: 6778
How to add Attributes (from CSAnswer) as selector columns to the Customer ID and Vendor ID lookups on the Customers and Vendors data entry screens respectively?
I tried to implement steps from StackOverflow article Add Attributes fields on Inventory Lookup, but it does not work for me.
Upvotes: 1
Views: 996
Reputation: 6778
Unlike the InventoryRawAttribute
used on the InventoryItem InventoryCD field, CustomerRawAttribute
and VendorRawAttribute
, decorating AcctCD field in the Customer and Vendor DACs respectively, explicitly define selector grid columns in their constructors. As explained in the API Reference, if no columns are specified for selector grid, all fields that have the PXUIField attribute's Visibility property set to PXUIVisibility.SelectorVisible
will be automatically added to the selector grid.
[PXDBString(InputMask = "", IsUnicode = true)]
[PXUIField(DisplayName = "Inventory ID", Visibility = PXUIVisibility.SelectorVisible)]
public sealed class InventoryRawAttribute : AcctSubAttribute
{
...
public InventoryRawAttribute()
: base()
{
...
PXDimensionSelectorAttribute attr = new PXDimensionSelectorAttribute(
DimensionName, SearchType, typeof(InventoryItem.inventoryCD));
...
}
...
}
[PXDBString(30, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Customer ID", Visibility = PXUIVisibility.Visible)]
public sealed class CustomerRawAttribute : AcctSubAttribute
{
...
public CustomerRawAttribute()
: base()
{
...
_Attributes.Add(new PXDimensionSelectorAttribute(
DimensionName, SearchType, typeof(Customer.acctCD),
typeof(Customer.acctCD),
typeof(Customer.acctName),
typeof(Customer.customerClassID),
typeof(Customer.status),
typeof(Contact.phone1),
typeof(Address.city),
typeof(Address.countryID),
typeof(Contact.eMail)));
...
}
}
[PXDBString(30, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Vendor", Visibility = PXUIVisibility.Visible)]
public sealed class VendorRawAttribute : AcctSubAttribute
{
...
public VendorRawAttribute(Type where)
{
...
PXDimensionSelectorAttribute attr;
_Attributes.Add(attr = new PXDimensionSelectorAttribute(
DimensionName, SearchType, typeof(Vendor.acctCD),
typeof(Vendor.acctCD),
typeof(Vendor.acctName),
typeof(Vendor.vendorClassID),
typeof(Vendor.status),
typeof(Contact.phone1),
typeof(Address.city),
typeof(Address.countryID));
...
}
}
To add attribute fields to the Customer ID selector, we'll start with implementation of a custom AttributesFieldWithColumnsAttribute:
public class AttributesFieldWithColumnsAttribute : CRAttributesFieldAttribute
{
private string[] attributeNames;
public AttributesFieldWithColumnsAttribute(string[] attributeNames, Type classIDField, Type noteIdField, Type[] relatedEntityTypes)
: base(classIDField, noteIdField)
{
this.attributeNames = attributeNames;
}
public string[] GetAttributeColumns()
{
return attributeNames;
}
public override void CacheAttached(PXCache sender)
{
_IsActive = true;
base.CacheAttached(sender);
}
public override void CommandPreparing(PXCache sender, PXCommandPreparingEventArgs e)
{
base.CommandPreparing(sender, e);
if (e.BqlTable == null && aggregateAttributes && sender.GetItemType().IsDefined(typeof(PXProjectionAttribute), true))
{
e.BqlTable = _BqlTable;
}
}
protected override void AttributeFieldSelecting(PXCache sender, PXFieldSelectingEventArgs e, PXFieldState state, string attributeName, int idx)
{
if (attributeNames.Any(attributeName.Equals))
{
state.Visible = true;
state.Visibility = PXUIVisibility.Visible;
//Out-of-the-box DisplayName is prefixed with "$Attributes$-" - if you need to take that off.
state.DisplayName = (!String.IsNullOrEmpty(state.DisplayName)) ? (state.DisplayName.Replace("$Attributes$-", "")) : attributeName;
}
base.AttributeFieldSelecting(sender, e, state, attributeName, idx);
}
protected override void AttributeCommandPreparing(PXCache sender, PXCommandPreparingEventArgs e, PXFieldState state, string attributeName, int iField)
{
if (e.Operation == PXDBOperation.External && e.Table != null &&
e.Table.IsDefined(typeof(PXSubstituteAttribute), false))
{
e = new PXCommandPreparingEventArgs(e.Row, e.Value, e.Operation, e.Table.BaseType, e.SqlDialect);
}
base.AttributeCommandPreparing(sender, e, state, attributeName, iField);
}
}
Our next step is to create a custom CustomerRawWithAttributes attribute, repeating functionality of the sealed CustomerRaw attribute and also extending the list of selector columns with the attributes defined in the AttributesFieldWithColumns attribute:
[PXDBString(30, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Customer ID", Visibility = PXUIVisibility.Visible)]
public sealed class CustomerRawWithAttributesAttribute : AcctSubAttribute
{
public const string DimensionName = "CUSTOMER";
public CustomerRawWithAttributesAttribute()
: base()
{
Type SearchType = typeof(Search2<Customer.acctCD,
LeftJoin<Contact, On<Contact.bAccountID, Equal<Customer.bAccountID>, And<Contact.contactID, Equal<Customer.defContactID>>>,
LeftJoin<Address, On<Address.bAccountID, Equal<Customer.bAccountID>, And<Address.addressID, Equal<Customer.defAddressID>>>>>,
Where<Match<Current<AccessInfo.userName>>>>);
_fields = new Type[] { typeof(Customer.acctCD), typeof(Customer.acctName),
typeof(Customer.customerClassID), typeof(Customer.status), typeof(Contact.phone1),
typeof(Address.city), typeof(Address.countryID), typeof(Contact.eMail) };
_Attributes.Add(new PXDimensionSelectorAttribute(DimensionName, SearchType, typeof(Customer.acctCD), _fields));
_SelAttrIndex = _Attributes.Count - 1;
((PXDimensionSelectorAttribute)_Attributes[_SelAttrIndex]).CacheGlobal = true;
Filterable = true;
}
public override void CacheAttached(PXCache sender)
{
base.CacheAttached(sender);
string name = _FieldName.ToLower();
sender.Graph.FieldSelecting.RemoveHandler(sender.GetItemType(), name, GetAttribute<PXDimensionSelectorAttribute>().FieldSelecting);
sender.Graph.FieldSelecting.AddHandler(sender.GetItemType(), name, FieldSelecting);
}
private readonly Type[] _fields;
private string[] _FieldList = null;
private string[] _HeaderList = null;
private void PopulateFields(PXCache sender)
{
if (_FieldList == null)
{
var attributeFields = new Dictionary<string, string>();
foreach (var fAttr in sender.GetAttributesOfType<AttributesFieldWithColumnsAttribute>(null, null))
{
foreach (string attribute in fAttr.GetAttributeColumns())
{
attributeFields.Add(attribute, fAttr.FieldName);
}
}
_FieldList = new string[_fields.Length + attributeFields.Count];
_HeaderList = new string[_fields.Length + attributeFields.Count];
for (int i = 0; i < _fields.Length; i++)
{
Type cacheType = BqlCommand.GetItemType(_fields[i]);
PXCache cache = sender.Graph.Caches[cacheType];
if (cacheType.IsAssignableFrom(typeof(BAccountR)) ||
_fields[i].Name == typeof(BAccountR.acctCD).Name ||
_fields[i].Name == typeof(BAccountR.acctName).Name)
{
_FieldList[i] = _fields[i].Name;
}
else
{
_FieldList[i] = cacheType.Name + "__" + _fields[i].Name;
}
_HeaderList[i] = PXUIFieldAttribute.GetDisplayName(cache, _fields[i].Name);
}
int index = _fields.Length;
foreach (var attributeField in attributeFields)
{
string fieldName = attributeField.Key + "_" + attributeField.Value;
var fs = sender.GetStateExt(null, fieldName) as PXFieldState;
if (fs != null)
{
_FieldList[index] = fieldName;
_HeaderList[index] = fs.DisplayName;
}
index++;
}
}
var attr = GetAttribute<PXDimensionSelectorAttribute>().GetAttribute<PXSelectorAttribute>();
attr.SetColumns(_FieldList, _HeaderList);
}
public void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (AttributeLevel == PXAttributeLevel.Item || e.IsAltered)
{
PopulateFields(sender);
}
PXFieldSelecting handler = GetAttribute<PXDimensionSelectorAttribute>().FieldSelecting;
handler(sender, e);
}
}
Our final step is to define 2 CacheAttached handlers within the CustomerMaint BLC extension to modify attributes of the Customer's AcctCD and Attributes field:
public class CustomerExt : PXGraphExtension<CustomerMaint>
{
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRemoveBaseAttribute(typeof(CustomerRawAttribute))]
[CustomerRawWithAttributes(IsKey = true)]
public void Customer_AcctCD_CacheAttached(PXCache sender) { }
[AttributesFieldWithColumns(new[] { "COMPREV", "COMPSIZE" },
typeof(Customer.customerClassID),
typeof(BAccount.noteID),
new[]
{
typeof(BAccount.classID),
typeof(Vendor.vendorClassID)
})]
public void Customer_Attributes_CacheAttached(PXCache sender) { }
}
Similar steps should be taken to add attribute fields to the Vendor ID selector. We create a custom VendorRawWithAttributes attribute, repeating functionality of the sealed VendorRaw attribute and also extending the list of selector columns with the attributes defined in the AttributesFieldWithColumns attribute:
[PXDBString(30, IsUnicode = true, InputMask = "")]
[PXUIField(DisplayName = "Vendor", Visibility = PXUIVisibility.Visible)]
public sealed class VendorRawWithAttributesAttribute : AcctSubAttribute
{
public const string DimensionName = "VENDOR";
public VendorRawWithAttributesAttribute()
: base()
{
Type SearchType = typeof(Search2<Vendor.acctCD,
LeftJoin<Contact, On<Contact.bAccountID, Equal<Vendor.bAccountID>, And<Contact.contactID, Equal<Vendor.defContactID>>>,
LeftJoin<Address, On<Address.bAccountID, Equal<Vendor.bAccountID>, And<Address.addressID, Equal<Vendor.defAddressID>>>>>,
Where<Match<Current<AccessInfo.userName>>>>);
_fields = new Type[] { typeof(Vendor.acctCD), typeof(Vendor.acctName),
typeof(Vendor.vendorClassID), typeof(Vendor.status), typeof(Contact.phone1),
typeof(Address.city), typeof(Address.countryID) };
_Attributes.Add(new PXDimensionSelectorAttribute(DimensionName, SearchType, typeof(Customer.acctCD), _fields));
_SelAttrIndex = _Attributes.Count - 1;
((PXDimensionSelectorAttribute)_Attributes[_SelAttrIndex]).CacheGlobal = true;
Filterable = true;
}
public override void CacheAttached(PXCache sender)
{
base.CacheAttached(sender);
string name = _FieldName.ToLower();
sender.Graph.FieldSelecting.RemoveHandler(sender.GetItemType(), name, GetAttribute<PXDimensionSelectorAttribute>().FieldSelecting);
sender.Graph.FieldSelecting.AddHandler(sender.GetItemType(), name, FieldSelecting);
}
private readonly Type[] _fields;
private string[] _FieldList = null;
private string[] _HeaderList = null;
private void PopulateFields(PXCache sender)
{
if (_FieldList == null)
{
var attributeFields = new Dictionary<string, string>();
foreach (var fAttr in sender.GetAttributesOfType<AttributesFieldWithColumnsAttribute>(null, null))
{
foreach (string attribute in fAttr.GetAttributeColumns())
{
attributeFields.Add(attribute, fAttr.FieldName);
}
}
_FieldList = new string[_fields.Length + attributeFields.Count];
_HeaderList = new string[_fields.Length + attributeFields.Count];
for (int i = 0; i < this._fields.Length; i++)
{
Type cacheType = BqlCommand.GetItemType(_fields[i]);
PXCache cache = sender.Graph.Caches[cacheType];
if (cacheType.IsAssignableFrom(typeof(BAccountR)) ||
_fields[i].Name == typeof(BAccountR.acctCD).Name ||
_fields[i].Name == typeof(BAccountR.acctName).Name)
{
_FieldList[i] = _fields[i].Name;
}
else
{
_FieldList[i] = cacheType.Name + "__" + _fields[i].Name;
}
_HeaderList[i] = PXUIFieldAttribute.GetDisplayName(cache, _fields[i].Name);
}
int index = _fields.Length;
foreach (var attributeField in attributeFields)
{
string fieldName = attributeField.Key + "_" + attributeField.Value;
var fs = sender.GetStateExt(null, fieldName) as PXFieldState;
if (fs != null)
{
_FieldList[index] = fieldName;
_HeaderList[index] = fs.DisplayName;
}
index++;
}
}
var attr = GetAttribute<PXDimensionSelectorAttribute>().GetAttribute<PXSelectorAttribute>();
attr.SetColumns(_FieldList, _HeaderList);
}
public void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (AttributeLevel == PXAttributeLevel.Item || e.IsAltered)
{
PopulateFields(sender);
}
PXFieldSelecting handler = GetAttribute<PXDimensionSelectorAttribute>().FieldSelecting;
handler(sender, e);
}
}
After that we need to define 2 CacheAttached handlers within the VendorMaint BLC extension to modify attributes of the Vendor's AcctCD and Attributes field:
public class VendorMaintExt : PXGraphExtension<VendorMaint>
{
[PXMergeAttributes(Method = MergeMethod.Append)]
[PXRemoveBaseAttribute(typeof(VendorRawAttribute))]
[VendorRawWithAttributes(IsKey = true)]
public void VendorR_AcctCD_CacheAttached(PXCache sender) { }
[AttributesFieldWithColumns(new[] { "CONFIGURAB", "COMPREV", "COMPSIZE" },
typeof(Vendor.vendorClassID),
typeof(BAccount.noteID),
new[]
{
typeof(BAccount.classID),
typeof(Customer.customerClassID)
})]
public void VendorR_Attributes_CacheAttached(PXCache sender) { }
}
Please note, there are no additional changes required in AR303000.aspx or AP303000.aspx to allow users search by attribute values in the customized selectors.
Upvotes: 2