Orchard CMS one ContentPart on several View - orchardcms

Exist model - \Models\HeaderPart.cs.
In Driver, want create CategoriesMenuDriver and use HeaderPart:
public class CategoriesMenuDriver : ContentPartDriver<HeaderPart>
{
private readonly dynamic shapeFactory;
private readonly IMenuAccessor menuAccessor;
public CategoriesMenuDriver(
IShapeFactory shapeFactory,
IMenuAccessor menuAccessor)
{
this.shapeFactory = shapeFactory;
this.menuAccessor = menuAccessor;
}
protected override DriverResult Display(HeaderPart part, string displayType, dynamic shapeHelper)
{
return this.ContentShape("Parts_CategoriesMenu", () => shapeHelper.Parts_CategoriesMenu(
MenuItems: this.menuAccessor.GetMenu<NavigationMenuItem>("UserAccount")));
}
}
But in CategoriesMenu view
var headerPart = (HeaderPart)Model.ContentItem.HeaderPart;
Model.ContentItem is null.
Can i get HeaderPart in View CategoriesMenu?

You should be able to access the part like this:
HeaderPart part = Model.ContentPart;
Although Model.ContentItem shouldn't be null there, which is strange
EDIT
If it also null you can try just setting it yourself:
protected override DriverResult Display(HeaderPart part, string displayType, dynamic shapeHelper)
{
return ContentShape("Parts_CategoriesMenu", () => shapeHelper.Parts_CategoriesMenu(
MenuItems: this.menuAccessor.GetMenu<NavigationMenuItem>("UserAccount"),
ContentPart: part
));
}
}

Related

How to efficiently return multiple DriverResults from the Display method?

This article describes how to write efficient DisplayDrivers for your Parts so that expensive code is only executed when the shape is actually displayed.
protected override DriverResult Display(MyPart part, string displayType, dynamic shapeHelper)
{
// return the shape
return ContentShape("Parts_MyPart", () => {
// do computations here
return shapeHelper.Parts_MyPart();
});
}
Now I'd like to make a Part that returns multiple DriverResults using the Combine method, with each DriverResult containing mostly the same data, which is fetched from the database. The problem is I can't think of a good way to make it efficient, since Combine doesn't take a Func parameter.
protected override DriverResult Display(MyPart part, string displayType, dynamic shapeHelper)
{
var data = ... // expensive query specific to the part
return Combined(
ContentShape("Parts_MyPart_A", () => shapeHelper.Parts_MyPart_A(
Data: data
)),
ContentShape("Parts_MyPart_B", () => shapeHelper.Parts_MyPart_B(
Data: data
)),
ContentShape("Pars_MyPart_C", ...
);
}
Can I achieve the same result so that the query is not executed if nothing is displayed and only executed once if multiple shapes are displayed?
I want to do this so I can display the same data on a ContentItem's Detail in different zones with different markup and styling. An alternative approach could be to return one shape that in turn pushes other shapes into different zones but then I would lose the ability to use Placement to control each of them individually.
I'd probably add a lazy field to your Part.
public class MyPart : ContentPart {
internal readonly LazyField<CustomData> CustomDataField = new LazyField<CustomData>();
public CustomData CustomData {
get { return CustomDataField.Value; }
}
}
public class CustomData {
...
}
public class MyPartHandler : ContentPartHandler {
private ICustomService _customService;
public MyPartHandler(ICustomService customService){
_customService = customService;
OnActivated<MyPart>(Initialize);
}
private void Initialize(ActivatedContentContext context, MyPart part){
part.CustomDataField.Loader(() => {
return _customService.Get(part.ContentItem.Id);
});
}
}
It will only be calculated if it is loaded and all the shapes will share the calculated value.

Cannot override tranlations (journal-lang)

I would like to override some Liferay's modules tranlations. I am fallowing: https://dev.liferay.com/develop/tutorials/-/knowledge_base/7-0/overriding-a-modules-language-keys
It works but not for all strings. First of all I would like to override some strings in journal-lang module (com.liferay.journal.lang), but this module doesn't have servlet context name. I have tried to skip that but it doesn't work. How can I override these strings?
I'm also trying to override some core strings (from portal-impl) but some of them remains untranslated. For example "Add Field" (add-field) from defining new form view. Any possible solutions?
journal-lang is a language components. In order to override some string from them you have to create a component for bundle com.liferay.journal.web or com.liferay.journal.service.
You've to create a CustomResourceBundle with extends ResourceBundle
#Component(immediate = true, property = { "language.id=en_US" }, service = ResourceBundle.class)
public class DefaultCustomResourceBundle extends ResourceBundle {
#Override
public Enumeration<String> getKeys() {
return _resourceBundle.getKeys();
}
#Override
protected Object handleGetObject(String key) {
return _resourceBundle.getObject(key);
}
private final ResourceBundle _resourceBundle = ResourceBundle.getBundle("content.Language", UTF8Control.INSTANCE);
}
And this should override translations accross the portal.

Orchard Query For Page with Show on a menu checked

Hi I want to make a "Query" and put a filter for returning all pages that has "Show on a menu" checked. I did not find a way to do that.. Is it possible?
Try something like this:
using Orchard.Localization;
using Orchard.Projections.Descriptors.Filter;
using Orchard.Navigation;
using IFilterProvider = Orchard.Projections.Services.IFilterProvider;
namespace MyProject.Filters
{
public class MenuPartFilter : IFilterProvider {
public Localizer T { get; set; }
public ProductPartFilter() {
T = NullLocalizer.Instance;
}
public void Describe(DescribeFilterContext describe)
{
describe.For(
"Content", // The category of this filter
T("Content"), // The name of the filter (not used in 1.4)
T("Content")) // The description of the filter (not used in 1.4)
// Defines the actual filter (we could define multiple filters using the fluent syntax)
.Element(
"MenuParts", // Type of the element
T("Menu Parts"), // Name of the element
T("Menu parts"), // Description of the element
ApplyFilter, // Delegate to a method that performs the actual filtering for this element
DisplayFilter // Delegate to a method that returns a descriptive string for this element
);
}
private void ApplyFilter(FilterContext context) {
// Set the Query property of the context parameter to any IHqlQuery. In our case, we use a default query
// and narrow it down by joining with the MenuPartRecord.
context.Query = context.Query.Join(x => x.ContentPartRecord(typeof (MenuPartRecord)));
}
private LocalizedString DisplayFilter(FilterContext context) {
return T("Content with MenuPart");
}
}
}

Unsupported Mapping Exception - Missing type map configuration in Automapper?

I'm sure I am missing something simple. First, I'll show all the code I have written to wire up the plumbing, then I'll show the exception message. Then, I'll set out what I have tried to fix it.
LicenceTrackerProfile
public class LicenceTrackerProfile : Profile
{
const string LicenceTrackerProfileName = "LicenceTrackerProfile";
public override string ProfileName
{
get { return LicenceTrackerProfileName; }
}
protected override void Configure()
{
// initialize mappings here
new ViewModelMappings(this).Initialize();
}
}
MapperBootstrapper
public class MapperBootstrapper
{
public void Configure()
{
var profile = new LicenceTrackerProfile();
AutoMapper.Mapper.Initialize(p => p.AddProfile(profile));
}
}
MappingBase
public abstract class MappingBase
{
private readonly Profile _profile;
protected MappingBase(Profile profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public Profile Profile
{
get { return _profile; }
}
}
UniversalMapper
public class UniversalMapper : IUniversalMapper
{
private readonly IMappingEngine _mappingEngine;
public UniversalMapper(IMappingEngine mappingEngine)
{
_mappingEngine = mappingEngine;
}
public virtual TDestination Map<TSource, TDestination>(TSource source, TDestination destination)
{
return _mappingEngine.Map(source, destination);
}
}
ViewModelMappings
public class ViewModelMappings : MappingBase, IMappingInitializer
{
private readonly Profile _profile;
public ViewModelMappings(Profile profile) : base(profile)
{
_profile = profile;
_profile.SourceMemberNamingConvention = new PascalCaseNamingConvention();
_profile.DestinationMemberNamingConvention = new PascalCaseNamingConvention();
}
public void Initialize()
{
// data to domain mappings
Profile.CreateMap<EFDTO.Enums.FileTypes, Domain.FileTypes>();
Profile.CreateMap<EFDTO.Licence, Domain.Licence>();
Profile.CreateMap<EFDTO.LicenceAllocation, Domain.LicenceAllocation>();
Profile.CreateMap<EFDTO.Person, Domain.Person>();
Profile.CreateMap<EFDTO.Software, Domain.Software>();
Profile.CreateMap<EFDTO.SoftwareFile, Domain.SoftwareFile>();
Profile.CreateMap<EFDTO.SoftwareType, Domain.SoftwareType>();
}
}
Note, the initialize method and Configure method are being called, so they're not being "missed".
Exception
Missing type map configuration or unsupported mapping.
Mapping types: Software -> Software LicenceTracker.Entities.Software
-> LicenceTracker.DomainEntities.Software
Destination path: Software
Source value: LicenceTracker.Entities.Software
Troubleshooting
Ignoring columns. I planned to ignore columns, starting with all and then eliminating them by un-ignoring them 1 by 1 until I found the problem columns. However, to my surprise, the error occurs when I ignore all columns:
Profile.CreateMap<EFDTO.Software, Domain.Software>()
.ForMember(software => software.Licences, e => e.Ignore())
.ForMember(software => software.Name, e => e.Ignore())
.ForMember(software => software.SoftwareFiles, e => e.Ignore())
.ForMember(software => software.Type, e => e.Ignore())
.ForMember(software => software.Description, e => e.Ignore())
.ForMember(software => software.Id, e => e.Ignore())
.ForMember(software => software.TypeId, e => e.Ignore()
.ForMember(software => software.ObjectState, e => e.Ignore());
The Domain entities have [DataContract] (at class level) and [DataMember] (at method level) attributes. I added each of those attributes to the EF entities as well.
Other than that, I am out of ideas. It all seems to be wired up correctly.
What did I miss?
I'm back to heroically answer my question.
The problem was in the Service which created the UniversalMapper object (forgive the sloppy code, it is not final yet):
public class LicenceTrackerService : ILicenceTrackerService, IDisposable
{
LicenceTrackerContext context = new LicenceTrackerContext();
private MapperBootstrapper mapperBootstrapper;
private IUniversalMapper mapper = new UniversalMapper(Mapper.Engine);
private IUnitOfWork unitOfWork;
public LicenceTrackerService()
{
unitOfWork = new UnitOfWork(context, new RepositoryProvider(new RepositoryFactories()));
mapperBootstrapper = new MapperBootstrapper();
mapperBootstrapper.Configure();
Database.SetInitializer(new LicenceTrackerInitializer());
context.Database.Initialize(true);
}
public int GetNumber()
{
return 42;
}
public List<LicenceTracker.DomainEntities.Software> GetSoftwareProducts()
{
var productsRepo = unitOfWork.Repository<Software>();
var list = productsRepo.Query().Select().ToList();
var softwareList = new List<LicenceTracker.DomainEntities.Software>();
foreach (var software in list)
{
var softwareProduct = new LicenceTracker.DomainEntities.Software();
softwareList.Add(Mapper.Map(software, softwareProduct));
}
return softwareList;
}
public void Dispose()
{
unitOfWork.Dispose();
}
}
I'm still not sure why, but initializing the mapper outside of the constructor (default value style) was not happy. By moving that instantiation into the constructor of the service, it worked:
private IUniversalMapper mapper;
public LicenceTrackerService()
{
mapper = new UniversalMapper(Mapper.Engine);
...
}
There's obviously something about static properties (Mapper.Engine) and default instantiations that I'm not understanding.
Anyway, no big deal as I was planning to inject the UniversalMapper into the service anyway.
Edit
I've actually figured out the problem for real now. It is an ordering thing. With Automapper, I had to initialize the mapper with the Profile before inserting the Mapper.Engine into the UniversalMapper.
Obviously, the Get aspect of the Mapper.Engine property is not just a memory reference to an object. And yes, a quick glance at the code inside Automapper confirms that.
So, assigning the result of the Get property to the _mappingEngine field of the UniversalMapper must happen after that engine has been configured.

MvxPickerViewModel Bind to Property of an Object

I have following code:
var pickerViewModel = new MvxPickerViewModel(this.mandantenPicker);
this.mandantenPicker.Model = pickerViewModel;
this.mandantenPicker.ShowSelectionIndicator = true;
mandantenAuswahlTextfield.InputView = this.mandantenPicker;
var set = this.CreateBindingSet<LoginView, LoginViewModel>();
set.Bind(pickerViewModel).For(p => p.SelectedItem).To(vm => vm.SelectedMandant);
set.Bind(pickerViewModel).For(p => p.ItemsSource).To(vm => vm.Mandanten);
set.Apply();
In the PickerView I don't have the expected values. Its showing the namespace of the object.
I'm binding to a list of "Mandants", a Mandant has FirstName/LastName. How can I set the "to be showed" value?
So something like:
set.Bind(pickerViewModel).For(p => p.ItemsSource).To(vm => vm.Mandanten)["FirstName"]; //stupid example but to show better what I mean/want
The MvxPickerViewModel currently uses GetTitle() to work out what to display:
public override string GetTitle(UIPickerView picker, int row, int component)
{
return _itemsSource == null ? "-" : RowTitle(row, _itemsSource.ElementAt(row));
}
protected virtual string RowTitle(int row, object item)
{
return item.ToString();
}
from https://github.com/slodge/MvvmCross/blob/v3/Cirrious/Cirrious.MvvmCross.Binding.Touch/Views/MvxPickerViewModel.cs
If you wanted to customise the strings displayed, you could:
inherit from MvxPickerViewModel and provide an override for RowTitle:
protected override string RowTitle(int row, object item)
{
return ((Mandant)item).FirstName;
}
or override the ToString in your Mandant object
or provide and use some ValueConverters from IEnumerable<Mandant> to IEnumerable<WrappedMandant> and Mandant to WrappedMandant where WrappedMandant provides the ToString() implementation you want to use:
public class WrappedMandant
{
public Mandant Mandant { get;set;}
public override string ToString()
{
return Mandant.FirstName;
}
}
If you wanted to customise the picker cells displayed further, then you could also override GetView and them provide a custom cell for each Mandant

Resources