My question is related to this one, but instead of changing a question I thought it Would be better to ask a new one.
I've now got a list of IContent items using the _taxonomyService.GetContentItems(term)
as suggested by #Bertrand Le Roy in the question mentioned above
But how do I turn this into a useful Html string, that I can update on the client via an ajax post?
public class HomeController : Controller
{
private readonly IOrchardServices _services;
private readonly IBlogService _blogService;
private readonly IBlogPostService _blogPostService;
private readonly IFeedManager _feedManager;
private readonly IArchiveConstraint _archiveConstraint;
private readonly ITaxonomyService _taxonomyService;
public HomeController(
IOrchardServices services,
IBlogService blogService,
IBlogPostService blogPostService,
IFeedManager feedManager,
IShapeFactory shapeFactory,
IArchiveConstraint archiveConstraint,
ITaxonomyService taxonomyService) {
_services = services;
_blogService = blogService;
_blogPostService = blogPostService;
_feedManager = feedManager;
_archiveConstraint = archiveConstraint;
T = NullLocalizer.Instance;
Shape = shapeFactory;
_taxonomyService = taxonomyService;
}
dynamic Shape { get; set; }
public Localizer T { get; set; }
public ActionResult Index()
{
return View();
}
[HttpPost]
public JsonResult ListByArchive(string path, IEnumerable<string> category)
{
try
{
// get year and month from path
path = path.ToLower().Substring(path.LastIndexOf(#"/archive/", StringComparison.Ordinal) + 9);
var date = path.Split('/');
var month = int.Parse(date[1]);
var year = int.Parse(date[0]);
// get list of terms ids from strings
var taxonomyPart = _taxonomyService.GetTaxonomyByName("Category");
var terms = category.Select(cat => _taxonomyService.GetTermByName(taxonomyPart.Id, cat)).ToList();
// get list of content items by term avoiding duplicates
var posts = new List<IContent>();
foreach (var term in terms)
{
var items = _taxonomyService.GetContentItems(term);
foreach (var item in items)
{
if (!posts.Select(p => p.Id).Contains(item.Id))
{
posts.Add(item);
}
}
}
// filter by date
var byDate = posts.Where(x =>
{
var publishedUtc = x.ContentItem.As<CommonPart>().CreatedUtc;
return
publishedUtc != null
&& publishedUtc.Value.Month == month
&& publishedUtc.Value.Year == year;
});
....
This gets me my list of IContent, but how do I get a the html for the rendered list ?
I've tried
var range = byDate.Select(x => _services.ContentManager.BuildDisplay(x, "Summary"));
var list = Shape.List();
list.AddRange(range);
dynamic viewModel = Shape.ViewModel().ContentItems(list);
var html = View((object)viewModel);
return Json(new { html = html });
but it returns an empty view,
{"html":{"MasterName":"","Model":[],"TempData":[],"View":null,"ViewBag":{},"ViewData":[],"ViewEngineCollection":[{"HostContainer":{}}],"ViewName":""}}
I have a view called ListByArchive.cshtml, that matches the one it the orchard.blog module.
As an aside, I should be returning a partial view result, instead of a jason result, but when I change the Action result type I get a 404. result from the server.
This is never going to work the way you think it does:
var html = View((object)viewModel);
The easiest way to return HTML representing the content item is to:
Mark your action with ThemedAttribute, ie. [Themed(false)]
Return new ShapeResult(this, viewModel) (full view) or new ShapePartialResult(this, viewModel) (partial view) instead of Json(new { html = html })
Rendering a shape/view to string inside the action is also possible, but way more tricky.
EDIT: I assumed you already have /Views/ViewModel.cshtml file in place. Like Bertrand Le Roy noted below - if it's not there, you need to add one to be able to create a shape using Shape.ViewModel().
Related
Is it possible to use a foreach loop in a BLC to iterate through the fields of a PXResultSet to get the FieldNames?
Is this doable? I can't seem to find a good way.
Thanks...
The PXResultset records are selected from a view. You can get the field names from the View.
Here's a full example:
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public override void Initialize()
{
// Get field list from data view
var dataView = new PXSelect<SOOrder>(Base);
string fieldNames = string.Join(",", GetFieldNames(dataView.View, Base.Caches));
// You don't need result set to get field names
PXResultset<SOOrder> resultSet = dataView.Select();
throw new PXException(fieldNames);
}
public string[] GetFieldNames(PXView view, PXCacheCollection caches)
{
var list = new List<string>();
var set = new HashSet<string>();
foreach (Type t in view.GetItemTypes())
{
if (list.Count == 0)
{
list.AddRange(caches[t].Fields);
set.AddRange(list);
}
else
{
foreach (string field in caches[t].Fields)
{
string s = String.Format("{0}__{1}", t.Name, field);
if (set.Add(s))
{
list.Add(s);
}
}
}
}
return list.ToArray();
}
}
When run, this example will show the fields names used in the data view in Sales Order screen SO301000 as an exception.
Field names are contained in Cache object. If you really need to get field names from PXResultset you need to iterate the cache types in the result set.
Example for first DacType (0) of result set:
public class SOOrderEntry_Extension : PXGraphExtension<SOOrderEntry>
{
public override void Initialize()
{
var dataView = new PXSelect<SOOrder>(Base);
PXResultset<SOOrder> resultSet = dataView.Select();
foreach (PXResult result in resultSet)
{
Type dacType = result.GetItemType(0);
foreach (var field in Base.Caches[dacType].Fields)
PXTrace.WriteInformation(field);
}
}
}
For example, in screen CR301000, source field is having 5 items right now, but I want to have 6 different items listing here, please advice how to do it. Thanks.
You can do it a few ways.
1) Customization to create a custom string/int list then override the dac attribute in the BLC to point to your custom list.
First create the custom stringlist:
public class CustomSourceAttribute : PXStringListAttribute
{
public const string _LEADPROSPECT = "1";
public const string _INITIALCONTACT = "2";
public const string _QUALIFIED = "3";
public const string _INITIALPRICE = "4";
public const string _PROPOSALSENT = "5";
public const string _POSITIVEPROPOSAL = "6";
public const string _VERBALCOMMIT = "7";
public const string _READYFORCONTRACT = "R";
public const string _CONTRACTSENT = "8";
public const string _CONTRACTSIGNED = "9";
public const string _CLOSEDLOST = "0";
public const string _TARGET = "T";
public CustomSourceAttribute()
: base(new string[]
{
_LEADPROSPECT,
_INITIALCONTACT,
_QUALIFIED,
_INITIALPRICE,
_PROPOSALSENT,
_POSITIVEPROPOSAL,
_VERBALCOMMIT,
_READYFORCONTRACT,
_CONTRACTSENT,
_CONTRACTSIGNED,
_CLOSEDLOST,
_TARGET
},
new string[]
{
"Lead/Prospecting",
"Initial Contact",
"Qualified",
"Initial Pricing Sent",
"Proposal Sent",
"Positive Proposal Discussions",
"Verbal Commitment",
"Ready for Contract",
"Contract Sent",
"Contract Signed",
"Closed Lost",
"Target"
})
{
}
}
Then override the dac attribute in a BLC Extension:
[PXDBString(1, IsFixed = true)]
[PXUIField(DisplayName = "Stage")]
[CustomSourceAttribute]
[PXDefault(CustomSourceAttribute._INITIALCONTACT)]
[PXMassUpdatableField]
protected void CROpportunity_StageID_CacheAttached(PXCache cache)
{
}
This sample is from the Opportunity screen but the same holds true w/ leads
2) Automation step to provide the new values.
See here for automation step locations
the second way doesn't require customization but does take more work if there is already automation steps defined. You need to create the custom list for each step that exists.
For something like Leads, I'd go with option 1 as there is tons of steps
Add the Values you want:
Just a quick note if you chose to add via automation - to add a new field click in the white space of the lookup field - You will see the current values - in the white space below - dbl click.
Here is an example of creating Acumatica dropdown list field. Add constatnt fields and Pair them in Tuple<T1, T2>[].
public class Operators
{
public const string And = "&&";
public const string Or = "||";
public class UI
{
public const string And = "And";
public const string Or = "Or";
}
public class OperatorsAttribute : PXStringListAttribute
{
public OperatorsAttribute() : base(new Tuple<string, string>[]
{
Pair(And, UI.And),
Pair(Or, UI.Or)
})
{}
}
}
Then you use the nested OperatorsAttribute attribute class on the field like this.
#region Operator
public abstract class operators : BqlString.Field<operators> { }
[PXDBString(15)]
[PXDefault(1)]
[OperatorsAttribute]
[PXUIField(FieldName = "Operators", DisplayName = "Operators")]
public virtual string Operators { get; set; }
#endregion
To change already existing fields, create Extension classes for DACs and Graphs. Here is the detailed documentation for creating extension classes — Acumatica ERP Customization Guide
In Orchard CMS I have a service that pulls data from an external data source, and loads the data into an Orchard Content Part. The Part has a migration that welds it with a title part, and I have a route so that my controller is being hit via a URL:
I am using a controller to access the item via a URL, much like the Blog Part controller. However I can't render my part...
The Blog Controller does similar to the following:
var asset = _assetService.Get(1234);
if (asset == null) return HttpNotFound();
var model = _services.ContentManager.BuildDisplay(asset);
return new ShapeResult(this, model);
But if I do this, the 'BuildDisplay' method looks for asset.ContentItem but this is null, despite deriving my part from 'ContentPart'.
What do I need to do to get my data to display?
If I understand correctly, you are trying to display only one part, and not a whole content item.
To display a single shape, you can do the following:
private readonly IAssetService _assetService;
public MyController(IShapeFactory shapeFactory, IAssetService assetService) {
_assetService = assetService;
Shape = shapeFactory;
}
public dynamic Shape { get; set; }
public ActionResult MyAction(int assetId) {
var asset = _assetService.Get(1234);
if (asset == null) return HttpNotFound();
// the shape factory can call any shape (.cshtml) that is defined
// this method assumes you have a view called SomeShapeName.cshtml
var model = Shape.SomeShapeName(asset);
return new ShapeResult(this, model);
}
!!Note:
This does not kick of the (display)driver of the part, it only returns the .cshtml with the given model
By having my part deriving from ContentPart, I can use the following Controller method:
private readonly IAssetService _assetService;
private readonly IOrchardServices _services;
public MyController(IShapeFactory shapeFactory, IAssetService assetService, IOrchardServices services) {
_assetService = assetService;
_services = services;
Shape = shapeFactory;
}
public dynamic Shape { get; set; }
public ActionResult MyAction(int assetId) {
var asset = _assetService.Get(1234);
if (asset == null) return HttpNotFound();
// this method assumes you have a view called Parts.Asset.cshtml (see the AssetPartDriver)
var model = _services.ContentManager.New("Asset");
var item = contentItem.As<AssetPart>();
item.Populate(asset) // Method that just populates the service loaded object into the ContentPart
return new ShapeResult(this, _services.ContentManager.BuildDisplay(item));
}
This will use the 'AssetPartDriver':
public class AssetPartDriver : ContentPartDriver<AssetPart>
{
protected override DriverResult Display(AssetPart part, string displayType, dynamic shapeHelper)
{
return ContentShape("Parts_Asset", () => shapeHelper.Parts_Asset()); // Uses Parts.Asset.cshtml
}
}
And in conjunction with the 'Placement.info' file renders on the screen:
<Placement>
<Match ContentType="Asset">
<Match DisplayType="Detail">
<Place Parts_Asset="Content"/>
</Match>
</Match>
</Placement>
The migration file combines my web service part with other Orchard parts:
public class Migrations : DataMigrationImpl
{
public int Create()
{
ContentDefinitionManager.AlterTypeDefinition("Asset", cfg => cfg
.WithPart("AssetPart")
.WithPart("AutoroutePart", builder => builder
.WithSetting("AutorouteSettings.AllowCustomPattern", "True"))
.Listable()
.Securable()
.Creatable(false));
ContentDefinitionManager.AlterPartDefinition("AssetPart", part => part
.WithDescription("A part that contains details of an individual Web Service loaded asset."));
return 1;
}
}
These additional parts are not yet used, but can be populated during creation and displayed individually using the placement file.
This is the first step of what I was trying to achieve!!
I just wondering how can I get an specific ContentItem in my controller.
I want to get the specific content Item and then display it's shape on my custom View..
PS: I also dont know how to get the ID of the content item should i use the ContentManager.Get(ID)
[HttpGet]
public ActionResult Index(string jobType, string location) {
var vm = new SearchForJobViewModel();
var items = new List<CustomPart>();
// Load the WhatsAround content items
IEnumerable<ContentItem> whatsAroundContentItems = ContentManager.Query().ForType("Custom").List();
foreach (ContentItem contentItem in whatsAroundContentItems)
{
ContentItemRecord contentItemRecord = contentItem.Record;
if (contentItem == null)
continue;
//CustomPart item = new CustomPart(contentItemRecord.Data);
//items.Add(item);
}
//Im also planning to pass the ContentItem in the view and then render it there
//return View("../JobSearchResults", items.Single(i => i.Name == "MyContentItem");
return View("../JobSearchResults", vm);
}
ADDITIONAL
[Themed]
[HttpGet]
public ActionResult Index(string jobType, string location) {
var vm = new SearchForJobViewModel();
vm.SelectedJobType = jobType;
vm.SelectedLocation = location;
//query all the content items
IEnumerable<ContentItem> items = ContentManager.Query().List();
foreach (ContentItem contentItem in items) {
ContentItemRecord contentItemRecord = contentItem.Record;
if (contentItem == null)
continue;
if (contentItemRecord.ContentType.Name == "CustomPage") {
// I just painfully search the contents just to get the ID of the specific content item that I want to display
// I dont know what table where I can see the ID on the content items
if (contentItemRecord.Id == 40) {
ContentItem ci = contentItem;
var test = ci.As<CustomPart>().Scripts; //custom part that I made
// the body part (raw html from the wysiwg editor)
// this I wil render it in my view
vm.Body = ci.As<BodyPart>().Text;
}
}
}
return View("../JobSearchResults", vm);
}
JobSearchResults.cshtml
#Html.Raw(Model.Body)
It's actually looks wierd but I'm trying something like utilizing Orchard CMS without using Orchard core (contentpart, drivers,handlers, etc)
Your can refer this.
http://skywalkersoftwaredevelopment.net/blog/getting-the-current-content-item-in-orchard
Or using the driver
protected override DriverResult Display(YourModulePart part, string displayType, dynamic shapeHelper){
var title = part.As<TitlePart>();
//here you can access the title part.
}
I'm trying to build a Workflow that add a Contact to a marketing list.
Everything seems to be fine, but when the code finishes firing, and I go to the marketing list -> members the contact is not in the list.
public class ContactToMList : CodeActivity
{
[Input("Contatto")]
[ReferenceTarget("contact")]
public InArgument<EntityReference> contact { get; set; }
[Input("Marketing List")]
[ReferenceTarget("list")]
public InArgument<EntityReference> MList { get; set; }
[Input("Inserimento")]
public InArgument<bool> inserimento { get; set; }
bool action = false;
private static IOrganizationService myService = null;
private static Log_Entity log = new Log_Entity(string.Empty, myService);
protected override void Execute(CodeActivityContext executionContext)
{
try
{
ITracingService tracingService = executionContext.GetExtension<ITracingService>();
// Create the context
IWorkflowContext context = executionContext.GetExtension<IWorkflowContext>();
IOrganizationServiceFactory serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();
// Create the Organiztion service
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
myService = service;
log.WriteLog("");
// Get the target entity from the context
Entity target = (Entity)context.InputParameters["Target"];
Guid contactiId = contact.Get<EntityReference>(executionContext).Id;
Guid ListId = MList.Get<EntityReference>(executionContext).Id;
bool insert = inserimento.Get<bool>(executionContext);
// Prepare DataContext by using AutoGenerated cs file
XrmDataContext datacontext = new XrmDataContext(service);
var MyContact = (from c in datacontext.ContactSet where c.ContactId == contactiId select c.Id).ToArray();
var MyList = (from l in datacontext.ListSet where l.Id == ListId select l).ToList().FirstOrDefault();
// tutti i membri della lista di marketing
var members = (from m in datacontext.ListMemberSet where m.ListId.Id == MyList.ListId select m.EntityId.Id).ToArray();
foreach (Guid id in members)
if (MyContact.FirstOrDefault() == id)
action = true;
if (insert && !action)
{
AddListMembersListRequest AddMemberRequest = new AddListMembersListRequest();
AddMemberRequest.ListId = ListId;
AddMemberRequest.MemberIds = MyContact;
// Use AddListMembersListReponse to get information about the request execution
AddListMembersListResponse AddMemberResponse = service.Execute(AddMemberRequest) as AddListMembersListResponse;
//service.Update(MyList);
}
else if (!insert && action)
{
RemoveMemberListRequest RemoveMemberRequest = new RemoveMemberListRequest();
RemoveMemberRequest.ListId = ListId;
RemoveMemberRequest.EntityId = MyContact.FirstOrDefault();
// Use AddListMembersListReponse to get information about the request execution
RemoveMemberListResponse RemoveMemberResponse = service.Execute(RemoveMemberRequest) as RemoveMemberListResponse;
// service.Update(MyList);
}
}
catch (Exception ex)
{
log.WriteLog(ex.Message);
}
}
}
aren't you erasing your values for AddMemberRequest.MemberIds after you set it?
EDIT:
Ok, I think I found it this time. Your public InArgument<bool> inserimento { get; set; } is likely the culprit.
In this case, your Workflow activity expects this to be defined upstream of the call to this Workflow. It's very likely statically set and never changed for both the Insert and Remove instances. If this is true, then it's being essentially hard coded for the Insert case, which makes the else if (!insert && action) evaluate to True for Remove and the if (insert && !action) evaluate to False for the Insert.
Since the code does work for Remove, it's reasonable to assume the bool action is working; therefore, I would start by looking into the other bool variable.
Let me know if I've missed it. (or if I'm right, I wouldn't mind the green check mark.)