How do I add a file/folder selector to a screen? - acumatica

I need a control that will allow me to select either a file or a folder (or both) from the user file system. Is there some kind of control that can be used in both existing screens and on new ones ?

I've combined both the file and folder selector in the same control but I think it will be easy enough for you to understand how to do either one.
I've made my example using an extension of SOOrderEntry, but it is the same principle for a new screen or another existing one.
In the PXDataSource element, add Datatrees and PXTreeDataMember elements as follow :
<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%" TypeName="PX.Objects.SO.SOOrderEntry" PrimaryView="Document">
<DataTrees>
<px:PXTreeDataMember TreeView="FilesTree" TreeKeys="FileKey" />
</DataTrees>
</px:PXDataSource>
In the formView element, add the PXTreeSelector :
<px:PXFormView ID="PXFormView2" runat="server" DataSourceID="ds" Style="z-index: 100" Width="100%" DataMember="Document" Caption="Order Summary"
NoteIndicator="True" FilesIndicator="True" LinkIndicator="True" EmailingGraph="PX.Objects.CR.CREmailActivityMaint,PX.Objects"
ActivityIndicator="True" ActivityField="NoteActivity" DefaultControlID="edOrderType" NotifyIndicator="True"
TabIndex="14900">
<Template>
<px:PXTreeSelector runat="server" TreeDataSourceID="ds" TreeDataMember="FilesTree" InitialExpandLevel="0" MinDropWidth="413" PopulateOnDemand="True" ShowRootNode="False" AllowEditValue="True" SelectOnFocus="False" DataField="ParentFolder" ID="edPathSelector">
<DataBindings>
<px:PXTreeItemBinding DataMember="FilesTree" TextField="FileName" ValueField="FilePath" DescriptionField="FilePath" ImageUrlField="Icon" />
</DataBindings>
</px:PXTreeSelector>
</Template>
</px:PXFormView>
Here is my DAC and graph extension. As you can see, the graph pretty much only calls the .NET function to enumerable files and folders. To specialize a control for either folder or files, you just need to return the right elements.
using PX.Data;
using PX.Web.UI;
using System;
using System.Collections;
using System.IO;
using System.Linq;
namespace PX.Objects.SO
{
[Serializable]
public class RowFilesTree : IBqlTable
{
[PXString(IsKey = true, IsUnicode = true)]
[PXUIField(DisplayName = "FileKey")]
public string FileKey { get; set; }
public class fileKey : IBqlField { }
[PXString]
[PXUIField(DisplayName = "Path")]
public string FilePath { get; set; }
public class filePath : IBqlField { }
[PXString]
[PXUIField(DisplayName = "Name")]
public string FileName { get; set; }
public class fileName : IBqlField { }
[PXString(250)]
public virtual string Icon { get; set; }
public abstract class icon : IBqlField {}
}
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public PXSelect<RowFilesTree> FilesTree;
protected virtual IEnumerable filesTree([PXString]string FilePath)
{
if (string.IsNullOrEmpty(FilePath))
{
var folders = Directory.GetLogicalDrives();
foreach (var folder in folders)
{
yield return new RowFilesTree()
{
FileKey = folder.Replace(':', '>'),
FileName = folder,
FilePath = folder,
Icon = Sprite.Tree.GetFullUrl(Sprite.Tree.Folder)
};
}
}
else
{
var folders = new string[0];
var files = new string[0];
try
{
var decode = FilePath.Replace('>', ':');
folders = Directory.GetDirectories(decode);
files = Directory.GetFiles(decode);
}
catch
{
}
foreach (var folder in folders)
{
yield return new RowFilesTree()
{
FileKey = folder.Replace(':', '>'),
FileName = Path.GetFileName(folder),
FilePath = folder,
Icon = Sprite.Tree.GetFullUrl(Sprite.Tree.Folder)
};
}
foreach (var file in files)
{
yield return new RowFilesTree()
{
FileKey = file.Replace(':', '>'),
FileName = Path.GetFileName(file),
FilePath = file,
Icon = Sprite.Tree.GetFullUrl(Sprite.Tree.Leaf)
};
}
}
}
}
}
Here is the final result :

Related

Acumatica - Adding Image in Sales Order Line

I am working on making a thumbnail image on the Sales Order lines for the Document Details when the InventoryID is selected. The image however does not populate to the grid whenever I select the InventoryID in the line. Here is what I have so far:
DAC Extension:
namespace PX.Objects.IN
{
public class InventoryItemExt : PXCacheExtension<InventoryItem>
{
#region ThumbnailURL
public abstract class thumbnailURL : IBqlField
{ }
[PXString]
public string ThumbnailURL { get; set; }
#endregion
}
}
Code Extension:
using PX.Data;
using PX.Objects.SO;
using System;
using PX.Objects.IN;
using PX.Web.UI;
namespace Combined
{
public class SOLineExt : PXCacheExtension<SOLine>
{
#region ThumbnailURL
public abstract class thumbnailURL : IBqlField
{ }
[PXString]
public string ThumbnailURL { get; set; }
#endregion
}
public class SOOrderEntryExt: PXGraphExtension<SOOrderEntry>
{
public void SOLine_RowSelecting(PXCache sender, PXRowSelectingEventArgs e,PXRowSelecting baseMethod)
{
baseMethod.Invoke(sender, e);
if(e.Row!=null)
{
var row = e.Row as SOLine;
if (row.InventoryID != null)
{
InventoryItem currentLineItem = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(this.Base, row.InventoryID);
if (row != null && !string.IsNullOrEmpty(currentLineItem.ImageUrl))
{
if(currentLineItem.StkItem==true)
{
InventoryItemMaint inventoryItemMaint = PXGraph.CreateInstance<InventoryItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
else
{
NonStockItemMaint inventoryItemMaint = PXGraph.CreateInstance<NonStockItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
}
}
}
}
}
}
ASPX Code:
Code in the Grid:
<px:PXGridColumn DataField="ThumbnailURL" Width="300px" Type="Icon" />
Code on the InventoryID SegmentMask:
<px:PXSegmentMask CommitChanges="True" ID="edInventoryID" runat="server" DataField="InventoryID" AllowEdit="True" >
<GridProperties>
<Columns>
<px:PXGridColumn Type="Icon" DataField="ThumbnailURL" Width="300px" AutoGenerateOption="Add" />
</Columns>
</GridProperties>
</px:PXSegmentMask>
I did find a post about adding an image to the InventoryID Selector and it has a different method of adding images to that grid, does the same apply here? Here is the other post: How to show images inside selector lookup?
I have changed my code above to match the other post but now I am receiving this error:
\App_RuntimeCode\SOOrderEntry.cs(61): error CS0103: The name 'ControlHelper' does not exist in the current context
\App_RuntimeCode\SOOrderEntry.cs(61): error CS0103: The name 'ControlHelper' does not exist in the current context
Adding code from the first answer below but now the grid column is showing up blank:
Update 1: FIXED
I have redone all the code above to answer 1 along with adding the code from the post of Ruslan's answer in the post above. The screenshot is still coming back the same.
Update 2:
I have got everything working or so it seemed. I am now receiving this error only sometimes and I'm not sure what the cause is. Ignore the CustomerID error that is because their credit balance is overdue.
Add reference to PX.Web.UI.dll from Acumatica's Bin folder or using PX.Web.UI; if you are writing code in the customization's Code Editor.
ControlHelper is a static helper class for making easier work with Acumatica's Web controls .
UPDATE 1
In the answer you have noted addition of the Image is done in the lookup of the Inventory Item Selector and add to the Grid the field of the SOLineExt. In your case you are adding it to the SOLine. Here is code which is doing that:
using PX.Data;
using PX.Objects.SO;
using System;
using PX.Objects.IN;
using PX.Web.UI;
namespace ClassLibrary1
{
public class SOLineExt : PXCacheExtension<SOLine>
{
#region ThumbnailURL
public abstract class thumbnailURL : IBqlField
{ }
[PXString]
public string ThumbnailURL { get; set; }
#endregion
}
public class SOOrderEntryExt: PXGraphExtension<SOOrderEntry>
{
public void SOLine_RowSelecting(PXCache sender, PXRowSelectingEventArgs e,PXRowSelecting baseMethod)
{
baseMethod?.Invoke(sender, e);
if(e.Row!=null)
{
var row = e.Row as SOLine;
if (row.InventoryID != null)
{
InventoryItem currentLineItem = PXSelect<InventoryItem, Where<InventoryItem.inventoryID, Equal<Required<InventoryItem.inventoryID>>>>.Select(this.Base, row.InventoryID);
if (row != null && !string.IsNullOrEmpty(currentLineItem.ImageUrl))
{
if(currentLineItem.StkItem==true)
{
InventoryItemMaint inventoryItemMaint = PXGraph.CreateInstance<InventoryItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
else
{
NonStockItemMaint inventoryItemMaint = PXGraph.CreateInstance<NonStockItemMaint>();
Guid[] files = PXNoteAttribute.GetFileNotes(inventoryItemMaint.Item.Cache, currentLineItem);
var fm = PXGraph.CreateInstance<PX.SM.UploadFileMaintenance>();
foreach (Guid fileID in files)
{
PX.SM.FileInfo fi = fm.GetFileWithNoData(fileID);
if (fi.FullName == currentLineItem.ImageUrl || fi.Name == currentLineItem.ImageUrl)
{
row.GetExtension<SOLineExt>().ThumbnailURL = ControlHelper.GetAttachedFileUrl(null, fileID.ToString());
break;
}
}
}
}
}
}
}
}
}
As you can see I have added the ThumbnailURL directly to SOLine.
Also now it's needed to create instance of the InventoryItemMaint or NonStockItemMaint depending on the Item Type(Stock/NonStock).
As a result you should get this:

Acumatica Processing Screen Updating ARTran Custom Field Needs To Also Update Custom Table field

We have a custom processing screen that is updating a custom field called UsrDateNotified in the ARTran table where the UsrDateNotified is prior to the RevisionDateReceived in a custom table ItemBaseDocument. We also need to update the UsrDateNotified field in table ItemBaseDocument which is linked to the InventoryID in ARTran. Our current code below validates for updating the ARTran table, but we are struggling with how to also update the related ItemBaseDocument for the selected ARTran records. What is the right approach for this scenario?
using System;
using System.Collections;
using System.Linq;
using PX.Data;
using PX.SM;
using PX.Objects.AR;
using PX.Objects.CR;
using PX.Objects.IN;
namespace DocCenter
{
public class UpdateLastNotified : PXGraph<UpdateLastNotified>
{
public PXFilter<UpdateLastNotifiedFilter> MasterView;
public PXCancel<UpdateLastNotifiedFilter> Cancel;
[PXFilterable]
public PXSelect<ARTran> DetailsView;
public UpdateLastNotified()
{
Cancel.SetCaption("Clear Filter");
this.DetailsView.Cache.AllowInsert = false;
this.DetailsView.Cache.AllowDelete = false;
this.DetailsView.Cache.AllowUpdate = true;
}
protected virtual IEnumerable detailsView()
{
UpdateLastNotifiedFilter filter = MasterView.Current as UpdateLastNotifiedFilter;
PXSelectBase<ARTran> cmd = new PXSelectJoinOrderBy<ARTran,
InnerJoin<InventoryItem, On<ARTran.inventoryID, Equal <InventoryItem.inventoryID>>,
InnerJoin<ItemBaseDocument, On<InventoryItemExt.usrDocumentNumber, Equal<ItemBaseDocument.baseDocumentCode>>,
InnerJoin<Contact, On<ARTranExt.usrContactID, Equal<Contact.contactID>>>>>,
OrderBy<Asc<ARTran.tranDate>>>(this);
cmd.WhereAnd<Where<ContactExt.usrNotificationPriority,
Equal<Current<UpdateLastNotifiedFilter.notificationPriority>>>>();
cmd.WhereAnd<Where<ARTranExt.usrDateNotified,
Less<ItemBaseDocument.revisionDateReceived>>>();
if (filter.BaseDocumentCode != null)
{
cmd.WhereAnd<Where<InventoryItemExt.usrDocumentNumber,
Equal<Current<UpdateLastNotifiedFilter.baseDocumentCode>>>>();
}
return cmd.Select();
}
public PXAction<UpdateLastNotifiedFilter> Process;
[PXProcessButton]
[PXButton(CommitChanges=true)]
[PXUIField(DisplayName = "Process")]
protected virtual IEnumerable process(PXAdapter adapter)
{
PXLongOperation.StartOperation(this, delegate()
{
foreach(ARTran tran in DetailsView.Select())
{
if (tran.Selected==true)
{
ARTranExt tranExt = tran.GetExtension<ARTranExt>();
ARInvoiceEntry tranEntry = new ARInvoiceEntry();
tranExt.UsrDateNotified = MasterView.Current.DateNotified;
tranEntry.Transactions.Update(tran);
tranEntry.Save.PressButton();
}
}
}
);
return adapter.Get();
}
[Serializable]
public class UpdateLastNotifiedFilter : IBqlTable
{
public static class NotificationPriority
{
public const string None = "N";
public const string Alert = "A";
public const string Express = "E";
public const string Shipment = "P";
public const string Subscription = "S";
}
#region NotificationPriority
public abstract class notificationPriority : PX.Data.IBqlField
{
}
[PXDBString(1, IsFixed = true)]
[PXDefault(NotificationPriority.None)]
[PXUIField(DisplayName = "Notification Type")]
[PXStringList(
new string[]
{
NotificationPriority.None,
NotificationPriority.Alert,
NotificationPriority.Express,
NotificationPriority.Shipment,
NotificationPriority.Subscription
},
new string[]
{
"None",
"Alert",
"Express",
"Shipment",
"Subscription"
})]
#endregion
#region BaseDocumentID
public abstract class baseDocumentCode : PX.Data.IBqlField
{
}
[PXString(50)]
[PXUIField(DisplayName="Document Number")]
[PXSelector(typeof(DocCenter.ItemBaseDocument.baseDocumentCode))]
public virtual String BaseDocumentCode
{
get;
set;
}
#endregion
#region DateNotified
public abstract class dateNotified : PX.Data.IBqlField
{
}
[PXDBDate()]
[PXDefault(typeof(AccessInfo.businessDate))]
[PXUIField(DisplayName = "Date Notified")]
public DateTime? DateNotified { get; set; }
#endregion
}
}
}
Your best bet would be to create a view that selects ItemBaseDocument.
PXSelect<ItemBaseDocument> ViewName;
In your for loop of your action button, you will want to create a new ItemBaseDocument object and set it equal to the corresponding ItemBaseDocument row entry. You can then update the date of this object, and when you execute your Save.PressButton() action, that should save the updates to that entry as well.
foreach(ARTran tran in DetailsView.Select())
{
if (tran.Selected==true)
{
ARTranExt tranExt = tran.GetExtension<ARTranExt>();
ARInvoiceEntry tranEntry = new ARInvoiceEntry();
tranExt.UsrDateNotified = MasterView.Current.DateNotified;
tranEntry.Transactions.Update(tran);
tranEntry.Save.PressButton();
//Target Added Code
ItemBaseDocument doc = PXSelect<ItemBaseDocument, Where<ItemBaseDocument.inventoryID,
Equal<Required<ARTran.inventoryID>>>>.Select(tran.InventoryID);
doc.UsrDateNotified = MasterView.Current.DateNotified;
}
}
Disclaimer: There may be a syntax error in the added code above. If there is, please let me know and I will fix it.

Using Unbound DACs in Process graph fails to run Processing functions

I'm creating a Process to import items from an external API source into records in Acumatica.
I've created an unbound DAC that is used to represent the entries available from the external API.
[Serializable]
public class ImportItem : IBqlTable
{
[PXBool]
[PXUIField(DisplayName = "Selected")]
public bool? Selected { get; set; }
public abstract class selected : IBqlField { }
[PXString]
[PXUIField(DisplayName = "External Ref Nbr")]
public string RefNbr { get; set; }
public abstract class refNbr : IBqlField { }
}
In a Process graph I implement the delegate of the main view to create and return the Resultset (normally generated from the API data). I then have a screen bound to this graph with a grid view that displays the items to allow the user to select the ones to import. The main Process delegate will then create the records in Acumatica for the selected items.
public class ImportItemsProcess : PXGraph<ImportItemsProcess>
{
public PXProcessing<ImportItem> ImportItems;
public PXCancel<ImportItem> Cancel;
public ImportItemsProcess()
{
ImportItems.SetProcessCaption("Import");
ImportItems.SetProcessAllCaption("Import All");
ImportItems.SetProcessDelegate(ProcessImportItems);
}
protected virtual IEnumerable importItems(PXAdapter adapter)
{
PXResultset<ImportItem> items = new PXResultset<ImportItem>();
/* Would create ImportItems from external API data here */
items.Add(new PXResult<ImportItem>(new ImportItem() { RefNbr = "1" }));
items.Add(new PXResult<ImportItem>(new ImportItem() { RefNbr = "2" }));
items.Add(new PXResult<ImportItem>(new ImportItem() { RefNbr = "3" }));
return items;
}
public static void ProcessImportItems(List<ImportItem> importItems)
{
throw new PXException("ProcessImportItems() has been called");
}
}
And the ASPX page:
<asp:Content ID="cont1" ContentPlaceHolderID="phDS" runat="Server">
<px:PXDataSource ID="ds" runat="server" Visible="True" Width="100%" PrimaryView="ImportItems" TypeName="APIImporter.ImportItemsProcess" >
</px:PXDataSource>
</asp:Content>
<asp:Content ID="cont2" ContentPlaceHolderID="phL" runat="Server">
<px:PXGrid ID="grid" runat="server" Height="400px" Width="100%" Style="z-index: 100"
AllowPaging="True" AllowSearch="True" AdjustPageSize="Auto" DataSourceID="ds" SkinID="Primary" TabIndex="1500" TemporaryFilterCaption="Filter Applied">
<Levels>
<px:PXGridLevel DataMember="ImportItems">
<RowTemplate>
<px:PXCheckBox ID="edSelected" runat="server" AlreadyLocalized="False" DataField="Selected" Text="Selected" CommitChanges="true">
</px:PXCheckBox>
<px:PXTextEdit ID="edRefNbr" runat="server" AlreadyLocalized="False" DataField="RefNbr" DefaultLocale="">
</px:PXTextEdit>
</RowTemplate>
<Columns>
<px:PXGridColumn DataField="Selected" TextAlign="Center" Type="CheckBox" Width="60px" CommitChanges="true">
</px:PXGridColumn>
<px:PXGridColumn DataField="RefNbr">
</px:PXGridColumn>
</Columns>
</px:PXGridLevel>
</Levels>
<AutoSize Container="Window" Enabled="True" MinHeight="200" />
</px:PXGrid>
</asp:Content>
When written as the simplified example here the Process delegate is never invoked. I suspect is has something to do with when the Process button is clicked the callback to the server runs the view's delegate function that re-creates the list of ImportItem objects and that the framework can not relate the newly created objects to the ones in the postback without a Key field. However, if I add an IsKey attribute to the RefNbr of the DAC...
[PXString(IsKey = true)]
[PXUIField(DisplayName = "External Ref Nbr")]
public string RefNbr { get; set; }
...now when selecting an item on the Screen I'm immediately given a line-level error with the message "Error: The argument is out of range. Parameter name: table".
In the data view delegate, you have to add the items to the Cache in addition to returning them. Try below:
protected virtual IEnumerable importItems()
{
int iCachedData = 0;
foreach (var row in ImportItems.Cache.Cached)
{
iCachedData++;
yield return row;
}
if (iCachedData == 0)
{
for (int iCounter = 1; iCounter <= 5; iCounter++)
{
ImportItem item = new ImportItem() { RefNbr = iCounter };
item = ImportItems.Insert(item);
ImportItems.Cache.SetStatus(item, PXEntryStatus.Held);
yield return item;
}
}
}
Good luck!
Try adding abstract classes for your DAC fields first.
If that doesn't resolve your issue please add your ASPX code to your question.
[Serializable]
public class ImportItem : IBqlTable
{
#region Selected
public abstract class selected : IBqlField { }
[PXBool]
[PXUIField(DisplayName = "Selected")]
public bool? Selected { get; set; }
#endregion
#region RefNbr
public abstract class refNbr : IBqlField { }
[PXString]
[PXUIField(DisplayName = "External Ref Nbr")]
public string RefNbr { get; set; }
#endregion
}
I have tried the Processing Graph functionality Using Bound DAC but not Database Table.
This is useful in the case when you want to Access some API and fetch the records in processing screen without saving into the Database. I am not including aspx code here.
The Highlight point in this code in [PXVirtualDAC] and the Key Field in DAC because of this we can achieve this functionality without creating a table. You can say that a Virtual DAC - without bound any database table.
public class TestUnboundProcessing : PXGraph<TestUnboundProcessing>
{
#region Unbound DAC
public class UnboundDAC : IBqlTable
{
#region Selected
[PXDBBool]
[PXUIField(DisplayName = "Selected")]
public virtual bool? Selected { get; set; }
public abstract class selected : PX.Data.IBqlField { }
#endregion
[PXDBString(50, IsUnicode = true,IsKey =true)]
[PXUIField(DisplayName = "Id")]
public string Id { get; set; }
public abstract class id : PX.Data.IBqlField { }
[PXDBString(100, IsUnicode = true)]
[PXUIField(DisplayName = "Author")]
public string Author { get; set; }
public abstract class author : PX.Data.IBqlField { }
[PXDBString(1000, IsUnicode = true)]
[PXUIField(DisplayName = "Body")]
public string Body { get; set; }
public abstract class body : PX.Data.IBqlField { }
}
#endregion
#region Processing Filter DAC
[Serializable]
public partial class TestFilter : PX.Data.IBqlTable
{
#region LastSyncDate
[PXDate()]
[PXDefault(typeof(AccessInfo.businessDate))]
[PXUIField(DisplayName = "Last Sync Date", Visibility = PXUIVisibility.Visible)]
public virtual DateTime? LastSyncDate { get; set; }
public abstract class lastSyncDate : PX.Data.IBqlField { }
#endregion
#region ProjectID
[PXDBString(10, IsUnicode = true)]
[PXUIField(DisplayName = "Project ID")]
public virtual String ProjectID { get; set; }
public abstract class projectID : PX.Data.IBqlField { }
#endregion
#region IssueID
[PXDBString(10, IsUnicode = true)]
[PXUIField(DisplayName = "Issue ID", Visibility = PXUIVisibility.SelectorVisible)]
public virtual String IssueID { get; set; }
public abstract class issueID : PX.Data.IBqlField { }
#endregion
}
#endregion
#region Filter + Delegate Overrides
public PXFilter<TestFilter> Filter;
public PXCancel<TestFilter> Cancel;
[PXVirtualDAC]
[PXFilterable]
public PXFilteredProcessing<UnboundDAC, TestFilter> UnboundView;
protected virtual IEnumerable unboundView()
{
GetUnboundDACList();
foreach (UnboundDAC item in UnboundView.Cache.Cached)
{
yield return item;
}
}
private void GetUnboundDACList()
{
UnboundView.Cache.Insert(new UnboundDAC() { Id = "1", Author = "Test 1", Body = "Comment 1" });
UnboundView.Cache.Insert(new UnboundDAC() { Id = "2", Author = "Test 2", Body = "Comment 2" });
UnboundView.Cache.Insert(new UnboundDAC() { Id = "3", Author = "Test 3", Body = "Comment 3" });
//return UnboundView.Cache;
}
#endregion
#region Constructor + Process
public TestUnboundProcessing()
{
TestFilter filter = Filter.Current;
UnboundView.SetProcessDelegate(delegate (List<UnboundDAC> docList)
{
UnboundProcessing(docList, filter);
}
);
}
#endregion
#region Processing functions
public static void UnboundProcessing(List<UnboundDAC> docList, TestFilter aFilter)
{
TestUnboundProcessing graph = CreateInstance<TestUnboundProcessing>();
graph.SaveRecords(graph, docList, aFilter);
}
public void SaveRecords(TestUnboundProcessing aProcessingGraph, List<UnboundDAC> docList, TestFilter aFilter)
{
bool isErrorOccured = false;
CRActivityMaint graph = PXGraph.CreateInstance<CRActivityMaint>();
foreach (UnboundDAC item in docList)
{
try
{
CRActivity addedActivity = new CRActivity();
addedActivity.UIStatus = ActivityStatusListAttribute.Completed;
addedActivity.Type = "N";
addedActivity.Location = item.Id;
addedActivity.Subject = item.Author;
addedActivity.Body = item.Body;
addedActivity.IsPrivate = true;
graph.Activities.Insert(addedActivity);
}
catch (Exception ex)
{
isErrorOccured = true;
PXProcessing<JRComment>.SetError(docList.IndexOf(item), ex.Message);
}
}
if (graph.Activities.Cache.Cached.Count() > 0)
{
graph.Actions.PressSave();
}
if (isErrorOccured)
{
throw new PXException("One or more record processed unsuccessful");
}
}
#endregion
#region Filter Events
protected virtual void TestFilter_LastSyncDate_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
UnboundView.Cache.Clear();
}
protected virtual void TestFilter_ProjectID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
UnboundView.Cache.Clear();
}
protected virtual void TestFilter_IssueID_FieldUpdated(PXCache sender, PXFieldUpdatedEventArgs e)
{
UnboundView.Cache.Clear();
}
#endregion

WebPart Properties Persistence

I've tried to Develop a Web Part that displays the text entered in the Property of a custom EditorPart,
but i have problem with the Properties persistence When i click on OK or Save and open the WebPart Properties the values of the properties revert to default,
same thing when i save the Page after editing the WebPart Properties and clicking on Apply OR OK buttons. Below is the code i implemented :
using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.WebPartPages;
using System.Web.UI.WebControls.WebParts;
using System.Collections.Generic;
using Microsoft.SharePoint.Administration;
namespace SH_Perso.WebPartPerso
{
[ToolboxItemAttribute(false)]
public class WebPartPerso : System.Web.UI.WebControls.WebParts.WebPart
{
[WebBrowsable(false), Personalizable(PersonalizationScope.User), DefaultValue("PERSO")]
public string MyPortalChoice { get; set; }
[WebBrowsable(false), Personalizable(PersonalizationScope.User), DefaultValue("Documents")]
public string MyAppChoice { get; set; }
[WebBrowsable(false), Personalizable(PersonalizationScope.User), DefaultValue("Tous les éléments")]
public string MyViewChoice { get; set; }
protected override void CreateChildControls()
{
base.CreateChildControls();
Controls.Add(new LiteralControl("PORTAIL :" + MyPortalChoice + "<br/> APPLICATION : " + MyAppChoice + "<br/> VUE : " + MyViewChoice));
}
/// <summary>
/// Creates custom editor parts here and assigns a unique id to each part
/// </summary>
/// <returns>All custom editor parts used by this web part</returns>
public override EditorPartCollection CreateEditorParts()
{
PEditorPart editorPart = new PEditorPart();
editorPart.Title = "Choix de la liste à afficher";
editorPart.ID = ID + "_editorPart";
EditorPartCollection editors = new EditorPartCollection(base.CreateEditorParts(), new EditorPart[] { editorPart });
return editors;
}
public override object WebBrowsableObject
{
get
{
return(this);
}
}
}
}
// THE EDITOR PART CLASS
class PEditorPart : EditorPart
{
public TextBox PortalChoices;
public TextBox AppChoices;
public TextBox ListViews;
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
PortalChoices = new TextBox();
AppChoices = new TextBox();
ListViews = new TextBox();
}
protected override void CreateChildControls()
{
Controls.Add(new LiteralControl("<div> <span style='font-weight:bold;'>Portail</span> "));
Controls.Add(PortalChoices);
Controls.Add(new LiteralControl("</div>"));
Controls.Add(new LiteralControl("<div> <span style='font-weight:bold;'>Listes disponibles</span> "));
Controls.Add(AppChoices);
Controls.Add(new LiteralControl("</div>"));
Controls.Add(new LiteralControl("<div> <span style='font-weight:bold;'>Listes des vues disponibles</span> "));
Controls.Add(ListViews);
Controls.Add(new LiteralControl("</div>"));
base.CreateChildControls();
ChildControlsCreated = true;
}
/// <summary>
/// Applies change in editor part ddl to the parent web part
/// </summary>
/// <returns></returns>
public override bool ApplyChanges()
{
try
{
EnsureChildControls();
WebPartPerso ParentWebPart = (WebPartPerso)WebPartToEdit;
if (ParentWebPart != null)
{
ParentWebPart.MyAppChoice = AppChoices.Text;
ParentWebPart.MyViewChoice = ListViews.Text;
ParentWebPart.MyPortalChoice = PortalChoices.Text;
}
ParentWebPart.SaveChanges();
// The operation was succesful
return true;
}
catch
{
// Because an error has occurred, the SyncChanges() method won’t be invoked.
return false;
}
}
/// <summary>
/// Reads current value from parent web part and show that in the ddl
/// </summary>
public override void SyncChanges()
{
EnsureChildControls();
WebPartPerso ParentWebPart = (WebPartPerso)WebPartToEdit;
if (ParentWebPart != null)
{
AppChoices.Text = ParentWebPart.MyAppChoice;
PortalChoices.Text = ParentWebPart.MyPortalChoice;
ListViews.Text = ParentWebPart.MyViewChoice.ToString();
}
}
}
Make WebBrowsable true
[WebBrowsable(true), Personalizable(PersonalizationScope.User), DefaultValue("PERSO")]
public string MyPortalChoice { get; set; }

ServiceStack metadata descriptions missing

In a given example code below the metadata page never gets the description specified in
[Description("GET account, all or by list of groups or by list of logins")]
Is there a special config that needs to be set in order for descriptions to show up in the metadata pages?
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using ServiceStack.ServiceHost;
using System.Runtime.Serialization;
using ServiceStack.WebHost.Endpoints;
namespace ConsoleApplication2
{
public class User
{
public User()
{
}
public int login;
public string group;
public string name;
}
[Description("GET account, all or by list of groups or by list of logins")]
[Route("/accounts")]
public class Accounts : IReturn<List<User>>
{
public string[] groups { set; get; }
public int[] logins { set; get; }
public Accounts() { }
public Accounts(params int[] logins)
{
this.logins = logins;
}
public Accounts(params string[] groups)
{
this.groups = groups;
}
}
public class Host : AppHostHttpListenerBase
{
public Host() : base("Test",
typeof(Accounts).Assembly)
{
}
public override void Configure(Funq.Container container)
{
SetConfig(new EndpointHostConfig {
EnableFeatures = Feature.All
});
}
}
public class Servce : IService
{
public object Get(Accounts request)
{
return new List<User>(){new User()};
}
}
class Program
{
static void Main(string[] args)
{
var host = new Host();
host.Init();
host.Start("http://+:12345/");
Console.ReadLine();
}
}
}
Navigating to http://localhost:12345/json/metadata?op=Accounts produces
<body>
<a id="logo" href="http://www.servicestack.net" title="servicestack"></a>
<h1>Test</h1>
<form>
<div>
<p><back to all web services</p>
<h2>Accounts</h2>
<div class="example">
<!-- REST Examples -->
...
In a recent release of ServiceStack, [Description] was deprecated in favour of [Api] and [ApiMember] which are also used in ServiceStack's Swagger support.
This is now an example of a fully annotated service:
[Api("Service Description")]
[Route("/swagger/{Name}", "GET", Summary = #"GET Summary", Notes = "GET Notes")]
[Route("/swagger/{Name}", "POST", Summary = #"POST Summary", Notes = "POST Notes")]
public class MyRequestDto
{
[ApiMember(Name="Name", Description = "Name Description",
ParameterType = "path", DataType = "string", IsRequired = true)]
public string Name { get; set; }
}

Resources