How to extend Orchard navigation module to add images to menu items - orchardcms

UPDATE: I've changed the original question drastically based on Bertrand's suggestions and my own findings. Now it provides an incomplete solution in its text instead of my own blind meanderings and commentary on Orchard, which were completely WRONG!
I need to display a menu using images instead of text, one standard, and another for when hovered/selected. The requirements for the site states that the end-user should be able to manage the menu item images. The standard navigation module now provides an HTML menu item, which is not what the end user wants. The customer wants a very simple, intuitive interface for configuring the sites many menus, and all menus are image-based.
Based on Bertrand's advice, and after realizing that Content Menu Item IS A CONTENT TYPE, I've created a new Content Part in the Admin Interface (not by code, I only want to write code for parts and content types when ultimately needed... I really want to see how far I can go with Orchard just by using the admin interface and templating/CSSing).
So, I've created a Menu Image Part, with two Content Picker fields added to it: Image and Hover Image. Then I've added this part to the Content Menu Item in the Manage Content Items admin interface.
Since I didn't write a Driver for it, the Model passed to the menu item template does not have an easily accessible property like #Model.Href... I've overriden the MenuItemLink-ContentMenuItem.cshtml with the following code so far:
#using Orchard.Core.Common.Models
#using Orchard.ContentManagement
#{
var contentManager = WorkContext.Resolve<IContentManager>();
var itemId = Model.Content.ContentItem.ContentMenuItemPart.Id;
ContentItem contentItem = contentManager.Get(itemId);
ContentField temp = null;
var menuImagePart = contentItem.Parts.FirstOrDefault(p => p.PartDefinition.Name == "MenuImagePart");
if (menuImagePart != null)
{
temp = menuImagePart.Fields.First();
}
}
<span>#temp</span>
#Model.Text
This yields the expected title for the Menu in a link, with a span before it with the following text:
Orchard.Fields.Fields.MediaPickerField
So all the above code (get the current content manager and the id of the ContentItem representing the ContentMenuItemPart, then use the content manager to get ContentItem itself, then linqing over its Parts to find the MenuImagePart (I can't use Get to get it because it requires a type and the MenuImagePart is not a type, it was created in the admin interface), then finally getting the first field for debugging purposes (this should be the Image field of the MenuImagePart I've created...)... all the above code actually got me to the Media Picker Field on my Meny Image Part...
What I'm not being able to do, and what makes me certainly a lot obtuse and stupid, is to find a way to read the MediaPickerField URL property! I've tried casting it to MediaPickerField, but I can't access its namespace from inside my template code above. I don't even know which reference to add to my theme to be able to add the following directive to it:
#using Orchard.Fields.Fields

I've finally succeeded in this task (thanks to Bertrand's direction).
UPDATE: And thanks again to Bertrand I've polished the solution which was running in circles, querying content items from the content manager when they were already available on the Model... now I'm leveraging the dynamic nature of content item, etc. And I'm finally satisfied with this solution.
It was necessary to create a new Content Part called Menu Image, then add this to the Content Type named Content Item Menu, and finally overriding the Content Item Menu template. This last part was the really tricky one. If it was not for Bertrand's directions the code bellow would have been smelly and daunting. The template ended up as follow:
#using Orchard.Utility.Extensions;
#using System.Dynamic
#{
/* Getting the menu content item
***************************************************************/
var menu = Model.Content.ContentItem;
/* Creating a unique CSS class name based on the menu item
***************************************************************/
// !!! for some reason the following code throws: 'string' does not contain a definition for 'HtmlClassify'
//string test = menu.ContentType.HtmlClassify();
string cssPrefix = Orchard.Utility.Extensions.StringExtensions.HtmlClassify(menu.ContentType);
var uniqueCSSClassName = cssPrefix + '-' + Model.Menu.MenuName;
/* Adds the normal and hovered styles to the html if any
***************************************************************/
if (menu.MenuImagePart != null)
{
if (!string.IsNullOrWhiteSpace(menu.MenuImagePart.Image.Url))
{
using(Script.Head()){
<style>
.#uniqueCSSClassName {
background-image: url('#Href(menu.MenuImagePart.Image.Url)');
width: #{#menu.MenuImagePart.Image.Width}px;
height: #{#menu.MenuImagePart.Image.Height}px;
display: block;
}
</style>
}
}
if (!string.IsNullOrWhiteSpace(menu.MenuImagePart.HoverImage.Url))
{
using(Script.Head()){
<style>
.#uniqueCSSClassName:hover {
background-image: url('#Href(menu.MenuImagePart.HoverImage.Url)');
width: #{#menu.MenuImagePart.HoverImage.Width}px;
height: #{#menu.MenuImagePart.HoverImage.Height}px;
}
</style>
}
}
}
}
<a class="#uniqueCSSClassName" href="#Model.Href">#Model.Text</a>
The only thing that I didn't understand is why I can't use HtmlClassify as an extension method with menu.ContentItem.HtmlClassify() and have to resort to calling the method as a standard static method (see the line with the comment `// !!! for some reason the following code throws´...)
Thanks again Bertrand!

Related

Orchard CMS - create vertical Navigation Menu next to Home

I am new to Orchard and trying to figure out how it works code-wise.
So, I have created a custom Content Type through the code and i am able to create Content Items under this Content Type. i have the 'Show on the Menu' checkbox on the editor page of the Content item. But when I check it and select the menu to which i want this newly created custom item to be added, it gets added as a vertical menu item whereas i need it to be added as a vertical submenu to one of the root items.
Please find images which describe what is happening now and what I need.
Current behavior
Expected behavior
Product2 is a custom content item and should be added as a entry in the vertical menu as shown in the 2nd image
This task is rather complex. There are several steps involved.
Understanding how to create a child theme
See the official documentation, create a child theme and enable it
Understanding the concept of shape alternates
See the official documentation
Configure the menu in admin area
Go to the admin area, click on Navigation in the menu and add some menu items and sub items, for example
[ Home (Content Menu Item) ]
[ Service (Content Menu Item) ]
[ Document Storage (Custom Link) ]
Once you have this structure Orchard renders this structure via a #Zone(Model.Navigation) call in a theme. You have to search where this call is located for yourself, it depends on the theme.
My child theme uses a Layout.cshtml alternate which calls #Zone(Model.Navigation) where needed like so
#{
Func<dynamic, dynamic> Zone = x => Display(x); // Zone as an alias for Display to help make it obvious when we're displaying zones
}
<div class="wrapper">
#* Navigation bar *#
#if (Model.Navigation != null)
{
<div id="layout-navigation" class="group navbar navbar-default" role="navigation">
#Zone(Model.Navigation)
</div>
}
...
</div>
Now, if Orchard renders the menu itself, it uses the Menu.cshtml shape template, thus the next step will be to provide a shape alternate for Menu.cshtml.
Creating a shape alternate for the menu in your child theme
Go to you child theme folder and add a file Views\Menu.cshtml and start rendering the menu there, for example
<ul class="nav navbar-nav">
#DisplayChildren(Model)
</ul>
The #DisplayChildren(Model) call will start rendering the menu items via the MenuItem.cshtml shape template, thus the next step will be to provide a shape alternate for MenuItem.cshtml.
Creating a shape alternate for menu items in your child theme
Go to you child theme folder and add a file Views\MenuItem.cshtml and start rendering the menu items. Here is the content of my MenuItem.cshtml file, which renders the menu items as <li> structure according to the bootstrap specs:
#*
this shape alternate is displayed when a <li> element is rendered
whereas the following code is based on Orchard.Core\Shapes\Views\Menu.cshtml
*#
#{
// odd formatting in this file is to cause more attractive results in the output.
var items = Enumerable.Cast<dynamic>((System.Collections.IEnumerable)Model);
}
#{
if (!HasText(Model.Text)) {
#DisplayChildren(Model)
}
else {
if ((bool) Model.Selected) {
Model.Classes.Add("current");
}
if (items.Any()) {
Model.Classes.Add("dropdown");
}
#* morphing the shape to keep Model untouched*#
Model.Metadata.Alternates.Clear();
Model.Metadata.Type = "MenuItemLink";
#* render the menu item only if it has some content *#
var renderedMenuItemLink = Display(Model);
if (HasText(renderedMenuItemLink)) {
var tag = Tag(Model, "li");
#tag.StartElement
#renderedMenuItemLink
if (items.Any()) {
<ul class="dropdown-menu">
#DisplayChildren(Model)
</ul>
}
#tag.EndElement
}
}
}
You can also provide alternates to override specific menu item types like the Custom Link. The file would then be MenuItemLink.cshtml with a content like
#*
this shape alternate is displayed when menu link is _not_ of type "Content Menu Item" otherwise MenuItemLink-ContentMenuItem.cshtml is used
whereas the following code is based on Orchard.Core\Shapes\Views\MenuItemLink.cshtml
*#
<a href="#Model.Href" #if (Model.Item.Items.Length > 0) { <text>class="dropdown-toggle" data-toggle="dropdown"</text> }>#Model.Text</a>
As you can see, a lot of work but pretty flexible.

How do you create a menuItem alternate in orchard for different Zones

I created an alternate for the main menu called MenuItemLink-main-menu-MainNavigation-MenuItem.cshtml because I want to render the menu differently in a Zone called main navigation. vs other places I am using the same menu on the page like the footer. I copied the MenuItem shape and renamed it (MenuItemLink-main-menu-MainNavigation-MenuItem.cshtml) everytime I run it I get an overflow because of the following line.
var renderedMenuItemLink = Display(Model);
can someone please explain to me why this is happening and the best way to create a zone based shapes for the navigation.
You copied markup from one shape (MenuItem) and paste it to another shape (MenuItemLink).
MenuItem calls Display for MenuItemLink
Then you calls Display for MenuItemLink inside MenuItemLink - this is an infinite loop.
For add alternate inside theme for MenuItemLink:
First. Create alternate for menu widget (Parts.MenuWidget.cshtml)
#using Orchard.ContentManagement;
#using Orchard.Widgets.Models;
#{
var widgetPart = ((IContent)Model.ContentItem).As<WidgetPart>();
Model.Menu.Zone = widgetPart.Zone;
}
Second. Create alternate for menu item (MenuItem.cshtml) and add one line (after line Model.Metadata.Type = "MenuItemLink";)
Model.Metadata.Type = "MenuItemLink";
(Model as Orchard.DisplayManagement.Shapes.Shape).Metadata.OnDisplaying(action =>
action.ShapeMetadata.Alternates.Add("MenuItemLink__Zone__" + (string)Model.Menu.Zone)
);
For add alternate inside theme for MenuItem:
First. Create alternate for menu widget (Parts.MenuWidget.cshtml)
#using Orchard.ContentManagement;
#using Orchard.Widgets.Models;
#using Orchard.DisplayManagement.Shapes;
#{
var widgetPart = ((IContent)Model.ContentItem).As<WidgetPart>();
var items = Model.Menu.Items as List<dynamic>;
AddMenuItemAlternate(items, widgetPart.Zone);
}
#functions{
public void AddMenuItemAlternate(List<dynamic> items, string zoneName)
{
foreach (var item in items)
{
item.Metadata.Alternates.Add("MenuItem__Zone__" + zoneName);
var subitems = (List<dynamic>)Enumerable.Cast<dynamic>(item.Items);
AddMenuItemAlternate(subitems, zoneName);
}
}
}
Update 2015.08:
I create module that adds widget name and zone name alternates for menu, menuItem and menu item link shapes. You can download one from orchard gallery https://gallery.orchardproject.net/List/Modules/Orchard.Module.MainBit.Navigation

Main Menu Navigation Item not linked to a content item but collapsing a sub menu

I have got a multi level navigation in Orchard CMS which shows sub menu items on hovering. When I click, it opens a content menu item. This works well on a PC but not on a mobile device. In case of a mobile device or touch device in general it should collapse the menu instead of jumping to another page.
I am looking for an easy and clean approach to solve this.
Is there a way to create such a menu item only acting as an opener for the sub menu items instead of actually linking to a page?
Some background information: It is actually a theme based on twittter bootstrap 3
You can extend the MenuItem shape (~/Core/Shapes/Views/MenuItem.cshtml) to differentiate between parent and children
So first create under {YourTheme}/Views new view called MenuItem.cshtml and paste in the following code
#{
// odd formatting in this file is to cause more attractive results in the output.
var items = Enumerable.Cast<dynamic>((System.Collections.IEnumerable)Model);
}
#{
if (!HasText(Model.Text))
{
#DisplayChildren(Model)
}
else
{
if ((bool)Model.Selected)
{
Model.Classes.Add("current");
}
#* morphing the shape to keep Model untouched*#
Model.Metadata.Alternates.Clear();
if (items.Any())
{
Model.Classes.Add("dropdown");
Model.Metadata.Type = "MenuItemLinkParent";
}
else
{
Model.Metadata.Type = "MenuItemLink";
}
#* render the menu item only if it has some content *#
var renderedMenuItemLink = Display(Model);
if (HasText(renderedMenuItemLink))
{
var tag = Tag(Model, "li");
#tag.StartElement
#renderedMenuItemLink
if (items.Any())
{
<ul>
#DisplayChildren(Model)
</ul>
}
#tag.EndElement
}
}
}
Now just create another view called MenuItemLinkParent.cshtml and create simple hyperlink placeholder that doesn't link anywhere
<a>#Model.Text</a>
Now any MenuItem which becomes parent will lose it's href link (you can also edit html structure and classes this way, if necessary for bootstrap). Easy and clean enough? :)

Sharepoint 2013 JSLink TaskList custom item rendering with out of the box options

I was not able to find the way to customize SP2013 Task List item rendering with JSLink in order to completely change the way that list item is rendered BUT also keeping all out of the box functionalities provided by default.
I mean, I'd like to display list elements as colored boxes, but also keeping sorting options, "..." (Open Menu) icon etc.
How can I achieve it? Is there any documentation where I can find lists of all internal fields like PercentComplete etc. which rendering can be overriden?
Any code snippets would be really appreciated!
Thanks a lot!
Take a look here
In a nutshell, what you want to do is, on the Templates object in the override context object add an object that is called Fields. In this object attributes named the same as the static name of a column(field) are used for rendering the value using the 'View' attribute. So, the example from the link is:
var overrideCtx = {};
overrideCtx.Templates = {};
// Override field data
overrideCtx.Templates.Fields = {
// PercentComplate = internal name of the % Complete
// View = you want to change the field rendering of a view
// <div ... = here we define what the output of the field will be.
'PercentComplete': { 'View' : '<div style="background: #F3F3F3; display:block; height: 20px; width: 100px;"><div style="background: #0072C6; height: 100%; width: <#=ctx.CurrentItem.PercentComplete.replace(" %", "")#>%;"></div></div>' }
};
// Register the override of the field
SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx
Using this method you will retain the default functionality in the other fields. Just make sure the column is visible in the current view.

Orchard CMS work with MediaPickerField defined in Migrations.cs

MediaPickerFields still elude me.
I've defined a new Part in Migrations.cs and added a Boolean column and a MediaPickerField to the part as follows:
SchemaBuilder.CreateTable("ImageContentPartRecord", table =>
table.ContentPartRecord()
.Column("DisplayImage", DbType.Boolean));
ContentDefinitionManager.AlterPartDefinition("ImageContentPart", builder =>
builder
.Attachable()
.WithField("ImageField", fld =>
fld.OfType("MediaPickerField")
.WithDisplayName("Image")));
Assuming I have ImageContentPart and ImageContentPartRecord classes, how can I retrieve the data from my MediaPickerField (url, dimensions, alt text, class, etc) in my Driver and in my Part Templates (Edit / Display)?
i.e. - Parts.ImageContent.cshtml (I want to accomplish something like this):
<div>
<img src="#Model.ImageField.Url" alt="#Model.ImageField.Alt" />
</div>
Any ideas?
Here is the solution:
In my Display Driver method, I make sure to pass my ImageContentPart(part) when building the ContentShape:
return ContentShape("Parts_ImageContent", () =>
shapeHelper.Parts_ImageContent(
Content: part));
Then in my Template View, I leverage the dynamic nature of Orchard's architecture to consume my MediaPickerField. To do this, you access the .ContentItem property on your part, then leaning on dynamics, chain the part name (.ImageContentPart) and then the field name (.ImageContent) to access the field.
#{
// Attempting to access MediaPickerField named 'ImageContent'
var image = Model.Content.ContentItem.ImageContentPart.ImageContent;
// Leaning on Orchard dynamics to access properties of the field
var url = image.Url;
}
<img src="#url" alt="#T(image.AlternateText)" />
Here is an exhaustive list of MediaPickerField properties from \\Orchard.Fields\Views\Fields\MediaPicker.cshtml:
#*
Alternate Text: #Model.ContentField.AlternateText
Class: #Model.ContentField.Class
Style: #Model.ContentField.Style
Alignment: #Model.ContentField.Alignment
Width: #Model.ContentField.Width
Height: #Model.ContentField.Height
Url: #Model.ContentField.Url
You can also display an image using this example, and add the attributes you need:
<img src="#Href(Model.ContentField.Url)" />
*#
Hopefully, if you stumble upon a similar problem, this will help out!

Resources