How to create a recent news list based on current news - orchardcms

I'm working on Orchard 1.10. The goal is to design a news website based on it. I have a problem that has not been solved.
That is how to generate a recent news list along with a news which I currently view in detail. I mean when I select a news, I need to show other news which has an Id lower than current news's ID along with it.
Any suggests are welcome. Thank you.

You can create a custom part and attach it to the News content type. Something like this:
public class RecentNewsPart : ContentPart {
}
public class RecentNewsPartDriver : ContentPartDriver<RecentNewsPart> {
private readonly IContentManager _contentManager;
public RecentNewsPartDriver(IContentManager contentManager) {
_contentManager = contentManager;
}
protected override DriverResult Display(RecentNewsPart part, string displayType, dynamic shapeHelper) {
return ContentShape("Parts_RecentNewsPart", () => {
// or however the date is stored on your news,
// maybe just the CommonPart CreatedUtc
var currentNewsPart = part.As<NewsPart>();
var currentNewsDate = currentNewsPart.Date;
var recentNews = _contentManager
.Query<NewsPart, NewsPartRecord>("News")
.Where(c => c.Date < currentNewsDate)
.OrderByDescending(c => c.Date)
.Slice(0, 10)
.ToList();
return shapeHelper.Parts_RecentNewsPart(Items: recentNews);
});
}
Placement.info:
<Match ContentType="News">
<Place Parts_RecentNewsPart="/AsideSecond:3" />
</Match>

Related

Orchard CMS: Create Content Items from External Data Source

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!!

Adding the target parameter to the Custom Link MenuItem in Orchard CMS

I have created my navigation menus in Orchard using a mix of Content Item and Custom Link Elements (parts of the website are outside the scope of the CMS). Now there are a couple of links that I need to open in a new window/tab, basically the target="_blank" behaviour.
SInce the original Custom Link does not have any parameters I tried to create an extended version of it. In the admin backend I went to "Content definition" looked up Custom Link and tried to create a copy of it, then add a target field that I could check for and use in my theme's Menu.cshtml file.
However I can't even get the basic carbon copy of the Custom Link item working. It has the same stereotype, same Parts, same Forms (none) as the original Custom Link, and it does appear in the list of items on the admin -> navigation window. However the item does not have a field for the URL/link. It only has the field for Menu Text, nothing else.
So my question is 2-tiered:
How can I get a carbon copy of the Custom Link item type working in my Orchard backend navigation?
When I have my copy of the Custom Link working and add a text field named target, how can I access its value in the Menu.cshtml view?
(I tried simply adding a URL field to my copy, that would then show up in the navigation editor, however the navigation itself would ignore it in the output and create a link to the content item id instead).
Any help is greatly appreciated!
Edit: Here are some screenshots to better illustrate the problem, maybe they can help pin down the problem.
Seems like you've done everything right. Please double check if MenuItemPart is there. This part is responsible for holding the URL information and displaying an editor for it. Not sure if this part is attachable though - if it's not, then make it so in the Content Definition\Parts pane.
Instead of hardwiring things inside Menu.cshtml, you should create a file named MenuItemLink-[YourTypeName].cshtml. This shape file will be used to display your custom menu items. Then you can access any fields via Model.Content object, eg. Model.Content.YourTypeName.FieldWithTargetName.Value.
You need to use the MenuItemPart because it has a few important functions integrated into Orchard.Core.
This works fine:
AdvancedMenuItemPartRecord:
public class AdvancedMenuItemPartRecord : ContentPartRecord
{
public virtual string Target { get; set; }
public virtual string Classes { get; set; }
}
AdvancedMenuItemPart:
public class AdvancedMenuItemPart : ContentPart<AdvancedMenuItemPartRecord>
{
public string Target
{
get { return Retrieve(x => x.Target); }
set { Store(x => x.Target, value); }
}
public string Classes
{
get { return Retrieve(x => x.Classes); }
set { Store(x => x.Classes, value); }
}
}
AdvancedMenuItemPartDriver:
public class AdvancedMenuItemPartDriver : ContentPartDriver<AdvancedMenuItemPart>
{
protected override string Prefix
{
get { return "AdvancedMenuItem"; }
}
protected override DriverResult Editor(AdvancedMenuItemPart part, dynamic shapeHelper)
{
return ContentShape("Parts_AdvancedMenuItem_Edit", () => shapeHelper.EditorTemplate(TemplateName: "Parts/AdvancedMenuItem", Model: part, Prefix: Prefix));
}
protected override DriverResult Editor(AdvancedMenuItemPart part, IUpdateModel updater, dynamic shapeHelper)
{
updater.TryUpdateModel(part, Prefix, null, null);
return Editor(part, shapeHelper);
}
}
AdvancedMenuItemPartHandler (ActivatingFilter add MenuItemPart to your AdvancedMenuItem dynamicaly):
public class AdvancedMenuItemPartHandler : ContentHandler
{
public AdvancedMenuItemPartHandler(IRepository<AdvancedMenuItemPartRecord> repository)
{
Filters.Add(StorageFilter.For(repository));
Filters.Add(new ActivatingFilter<MenuItemPart>("AdvancedMenuItem"));
}
}
Placement.info:
<Place Parts_AdvancedMenuItem_Edit="Content:11"/>
Migrations:
public int UpdateFrom2()
{
SchemaBuilder.CreateTable("AdvancedMenuItemPartRecord",
table => table
.ContentPartRecord()
.Column<string>("Target")
.Column<string>("Classes")
);
ContentDefinitionManager.AlterPartDefinition("AdvancedMenuItemPart", part => part
.WithDescription(""));
ContentDefinitionManager.AlterTypeDefinition("AdvancedMenuItem", cfg => cfg
.WithPart("AdvancedMenuItemPart")
.WithPart("MenuPart")
.WithPart("CommonPart")
.WithIdentity()
.DisplayedAs("Custom Link Advanced")
.WithSetting("Description", "Custom Link with target and classes fields")
.WithSetting("Stereotype", "MenuItem")
// We don't want our menu items to be draftable
.Draftable(false)
// We don't want the user to be able to create new ActionLink items outside of the context of a menu
.Creatable(false)
);
return 3;
}
MenuItemLink-AdvancedMenuItem.cshtml:
#{
var advancedPart = Model.Content.AdvancedMenuItemPart;
var tag = new TagBuilder("a");
tag.InnerHtml = WebUtility.HtmlDecode(Model.Text.Text);
tag.MergeAttribute("href", Model.Href);
if (!string.IsNullOrWhiteSpace(advancedPart.Target)) {
tag.MergeAttribute("target", advancedPart.Target);
}
if (!string.IsNullOrWhiteSpace(advancedPart.Classes))
{
tag.AddCssClass(advancedPart.Classes);
}
}
#Html.Raw(tag.ToString(TagRenderMode.Normal))

Orchard Custom Workflow Activity

I have built a custom module in Orchard that creates a new part, type and a custom activity but I'm struggling with the last part of what I need to do which is to create a copy of all the content items associated with a specific parent item.
For instance, when someone creates a "Trade Show" (new type from my module), various subpages can be created off of it (directions, vendor maps, etc.) since the client runs a single show at a time. What I need to do is, when they create a new Trade Show, I want to get the most recent prior show (which I'm doing via _contentManager.HqlQuery().ForType("TradeShow").ForVersion(VersionOptions.Latest).ForVersion(VersionOptions.Published).List().Last() (positive that's not the most efficient way, but it works and the record count would be ~10 after five years), then find all of those child pages that correlate to that old show and copy them into new Content Items. They have to be a copy because on occasion they may have to refer back to parts with the old shows, or it could change, etc. All the usual reasons.
How do I go about finding all of the content items that reference that prior show in an Activity? Here is my full class for the Activity:
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.Autoroute.Services;
using Orchard.ContentManagement;
using Orchard.Localization;
using Orchard.Projections.Models;
using Orchard.Projections.Services;
using Orchard.Workflows.Models;
using Orchard.Workflows.Services;
using Orchard.Workflows.Activities;
namespace Orchard.Web.Modules.TradeShows.Activities
{
public class TradeShowPublishedActivity : Task
{
private readonly IContentManager _contentManager;
private readonly IAutorouteService _autorouteService;
private readonly IProjectionManager _projectionManager;
public TradeShowPublishedActivity(IContentManager contentManager, IAutorouteService autorouteService, IProjectionManager projectionManager)
{
_contentManager = contentManager;
_autorouteService = autorouteService;
_projectionManager = projectionManager;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public override LocalizedString Category
{
get { return T("Flow"); }
}
public override LocalizedString Description
{
get { return T("Handles the automatic creation of content pages for the new show."); }
}
public override string Name
{
get { return "TradeShowPublished"; }
}
public override string Form
{
get { return null; }
}
public override IEnumerable<LocalizedString> GetPossibleOutcomes(WorkflowContext workflowContext, ActivityContext activityContext)
{
yield return T("Done");
}
public override IEnumerable<LocalizedString> Execute(WorkflowContext workflowContext, ActivityContext activityContext)
{
var priorShow = _contentManager.HqlQuery().ForType("TradeShow").ForVersion(VersionOptions.Latest).ForVersion(VersionOptions.Published).List().Last();
var tradeShowPart = priorShow.Parts.Where(p => p.PartDefinition.Name == "TradeShowContentPart").Single();
//new show alias
//workflowContext.Content.ContentItem.As<Orchard.Autoroute.Models.AutoroutePart>().DisplayAlias
yield return T("Done");
}
}
}
My Migrations.cs file sets up the part that is used for child pages to reference the parent show like this:
ContentDefinitionManager.AlterPartDefinition("AssociatedTradeShowPart", builder => builder.WithField("Trade Show", cfg => cfg.OfType("ContentPickerField")
.WithDisplayName("Trade Show")
.WithSetting("ContentPickerFieldSettings.Attachable", "true")
.WithSetting("ContentPickerFieldSettings.Description", "Select the trade show this item is for.")
.WithSetting("ContentPickerFieldSettings.Required", "true")
.WithSetting("ContentPickerFieldSettings.DisplayedContentTypes", "TradeShow")
.WithSetting("ContentPickerFieldSettings.Multiple", "false")
.WithSetting("ContentPickerFieldSettings.ShowContentTab", "true")));
Then, my child pages (only one for now, but plenty more coming) are created like this:
ContentDefinitionManager.AlterTypeDefinition("ShowDirections", cfg => cfg.DisplayedAs("Show Directions")
.WithPart("AutoroutePart", builder => builder.WithSetting("AutorouteSettings.AllowCustomPattern", "true")
.WithSetting("AutorouteSettings.AutomaticAdjustmentOnEdit", "false")
.WithSetting("AutorouteSettings.PatternDefinitions", "[{Name:'Title', Pattern: '{Content.Slug}', Description: 'international-trade-show'}]")
.WithSetting("AutorouteSettings.DefaultPatternIndex", "0"))
.WithPart("CommonPart", builder => builder.WithSetting("DateEditorSettings.ShowDateEditor", "false"))
.WithPart("PublishLaterPart")
.WithPart("TitlePart")
.WithPart("AssociatedTradeShowPart") /* allows linking to parent show */
.WithPart("ContainablePart", builder => builder.WithSetting("ContainablePartSettings.ShowContainerPicker", "true"))
.WithPart("BodyPart"));
So you have the Trade Show content item, the next step will be to find all parts with a ContentPickerField, then filter that list down to those where the field contains your show's ID.
var items = _contentManager.Query().List().ToList() // Select all content items
.Select(p => (p.Parts
// Select all parts on content items
.Where(f => f.Fields.Where(d =>
d.FieldDefinition.Name == typeof(ContentPickerField).Name &&
// See if any of the fields are ContentPickerFields
(d as ContentPickerField).Ids.ToList().Contains(priorShow.Id)).Any())));
// That field contains the Id of the show
This could get expensive depending on how many content items are in your database.

Add wrapper to widgets only in a specific zone

I'm adding a custom wrapper to widgets in placement.info like this:
<Match ContentType="Widget">
<Place Parts_Common_Body="Content:5;Wrapper=Wrapper_AsideWidget" />
</Match>
This works just fine, but I need to to limit the application of the custom wrapper to only widgets in a few specific zones. Right now they're being applied to widgets in all zones. What's the best way to achieve this? It would be perfect if the Match element could be scoped to a zone but I don't think that's possible.
Any advice or suggestions?
UPDATE
Here's the final solution I came up with. It applies the custom wrapper to any widgets in the aside zones. Just dropped the class into the theme.
public class AsideWidgetShapeProvider : IShapeTableProvider
{
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("Widget")
.OnDisplaying(displaying =>
{
var shape = displaying.Shape;
ContentItem contentItem = shape.ContentItem;
if (contentItem != null)
{
var zoneName = contentItem.As<WidgetPart>().Zone;
if (zoneName == "AsideFirst" || zoneName == "AsideSecond")
{
shape.Metadata.Wrappers.Add("Wrapper_AsideWidget");
}
}
});
}
}
You can create a Shape Table Provider that describes the behavior of the Parts_Common_Body shape and applies your wrapper conditionally. Just add a class such as the following to your module, and Orchard will process it when it builds the shape table.
Example:
using Orchard.ContentManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.Widgets.Models;
namespace MyModule {
public class ShapeTable : IShapeTableProvider {
public void Discover(ShapeTableBuilder builder) {
builder.Describe("Parts_Common_Body")
.OnDisplaying(ctx => {
var shape = ctx.Shape;
// Parts_Common_Body has a ContentPart property, so you can
// do this to get at the content item.
var contentItem = ((IContent)shape.ContentPart).ContentItem;
if (contentItem.ContentType == "Widget") { // content type to check for
var widgetPart = contentItem.As<WidgetPart>();
if (widgetPart.Zone == "AsideFirst") { // zone to check for
// Condition is met, let's add the wrapper.
ctx.ShapeMetadata.Wrappers.Add("Wrapper_AsideWidget");
}
}
});
}
}
}

Using Orchard CMS how do I change the heading displayed in the list of content items

I have created a new module in Orchard CMS, i have a new event part that has a whole bunch of custom fields. How do i change the heading displayed in the list of content?
Thanks
It is possible to set the meta data in the Handler
protected override void GetItemMetadata(GetContentItemMetadataContext context)
{
// We will set the display text, appears in content list
var e = context.ContentItem.As<EventPart>();
if (e != null)
{
context.Metadata.DisplayText = e.Name;
}
}
You could use the ITitleAspect method:
public interface ITitleAspect : IContent {
string Title { get; }
}
As seen in David Hayden's fine post.

Resources