What's the best way to extent the Menu part in orchard ?
I want to use the normal menu part for most content types.
But I also want a Custom MainNavigationMenuPart. that has all the things the menu part has but adds a media picker field and a text field to it. - These items that will display on menu rollover.
Option 1
I think this is the best way to go ...
I've looked at writing a custom Menu part - but it seem like a lot of functionality for how the menu part currently work is there, I'm not sure how best to tap into this in a DRY way.
I can add a MenuPartItem to my customPart, so main menu part model would look like this
public class MainMenuPart : ContentPart<MainMenuRecord>
{
public MenuPart MenuPart { get; set; }
public string ShortDescription
{
get { return Record.ShortDescription; }
set { Record.ShortDescription = value; }
}
}
But ...
how do I render this on in the editor view for the part ?
I want to use the exiting MenuPartItemEditor.
how do I save this information in the record for the part?
Option 2
I've looked also at adding fields to the menu part (via cms).
My menu part now looks like this on the back end
Here I have customise the admin view for the menu depending on the content type.
Buy creating a Parts.Navigation.Menu.Edit.cshtml in Views/EditorTemplates, in my custom them I have access to the menu part, but I can seem to control the display of the fileds I have added to the part. (menu image, highlight, and short description)
Here is the custom Parts.Navigation.Menu.Edit.cshtml (original found in Orchard.Core/Navigation/Views/EditorTemplates/Parts.Navigation.Menu.Edit.cshtml)
#model Orchard.Core.Navigation.ViewModels.MenuPartViewModel
#using Orchard.ContentManagement
#using Orchard.Core.Navigation.Models;
#if (!Model.ContentItem.TypeDefinition.Settings.ContainsKey("Stereotype") || Model.ContentItem.TypeDefinition.Settings["Stereotype"] != "MenuItem")
{
if (Model.ContentItem.ContentType == "StandardIndexPage" ||
Model.ContentItem.ContentType == "AlternateIndexPage" ||
Model.ContentItem.ContentType == "MapIndexPage")
{
var sd = ((dynamic)Model.ContentItem).MenuPart.ShortDescription;
#sd
<fieldset>
#Html.HiddenFor(m => m.OnMenu, true)
#Html.HiddenFor(m => m.CurrentMenuId, Model.CurrentMenuId)
<div>
<label for="MenuText">#T("Menu text (will appear on main menu)")</label>
#Html.TextBoxFor(m => m.MenuText, new { #class = "text-box single-line" })
<span class="hint">#T("The text that should appear in the menu.")</span>
</div>
</fieldset>
}
else
{
<fieldset>
#Html.EditorFor(m => m.OnMenu)
<label for="#Html.FieldIdFor(m => m.OnMenu)" class="forcheckbox">#T("Show on menu")</label>
<div data-controllerid="#Html.FieldIdFor(m => m.OnMenu)" class="">
<select id="#Html.FieldIdFor(m => m.CurrentMenuId)" name="#Html.FieldNameFor(m => m.CurrentMenuId)">
#foreach (ContentItem menu in Model.Menus)
{
#Html.SelectOption(Model.CurrentMenuId, menu.Id, Html.ItemDisplayText(menu).ToString())
}
</select>
<span class="hint">#T("Select which menu you want the content item to be displayed on.")</span>
<label for="MenuText">#T("Menu text")</label>
#Html.TextBoxFor(m => m.MenuText, new { #class = "text-box single-line" })
<span class="hint">#T("The text that should appear in the menu.")</span>
</div>
</fieldset>
}
}
else
{
<fieldset>
<label for="MenuText">#T("Menu text")</label>
#Html.TextBoxFor(m => m.MenuText, new { #class = "textMedium", autofocus = "autofocus" })
<span class="hint">#T("The text that should appear in the menu.")</span>
#Html.HiddenFor(m => m.OnMenu, true)
#Html.HiddenFor(m => m.CurrentMenuId, Request["menuId"])
</fieldset>
}
I've also tried to control the display of fields using the placement.info in the theme
<Match ContentType="StandardIndexPage">
<Place Fields_Boolean_Edit-Highlight="-"/>
</Match>
with no success.
Related
It's handy to use a Display attribute for model properties in MVC:
Model:
[Display(Name="Your Name:")]
public string Name { get; set; }
View:
#Html.LabelFor(m => m.Name)
#Html.EditorFor(m => m.Name)
But ... is it possible to use the Display attribute for naming the individual choices for radio buttons? The following is what I use now, but the 'Label for...' tag is a little inconsistent with the rest of the view. Anyone?
<div class="radio-inline">
#Html.RadioButtonFor(m => m.OpenToPublic, true, new { id = "isOpenToPublic" })
<label for="isOpenToPublic">Open to the Public</label>
</div>
<div class="radio-inline">
#Html.RadioButtonFor(m => m.OpenToPublic, false, new { id = "isInviteesOnly" })
<label for="isInviteesOnly">Invitees Only</label>
</div>
I like the code above, since using the Label tag results in being able to click on the label text to select the radio button, and the styling of the text is correct. I just wonder if there's a way to do this with data annotations on the model's property for the radio button.
Thanks,
Brian
I am having difficulties in wiring up a custom EditorTemplate to a grid within an MVC 5 application. I have an integer field that only accepts a 1 or 2 as a value. Rather than using a standard numeric text box or slider control, I'd like to wire this up using buttons (via Bootstrap's group buttons). If the user clicks on the first button, the value should be set to 1, otherwise it should be set to 2.
The problem that I'm experiencing is that when the user clicks on the "edit" button, the "Level" value never gets applied to the editor template. The template displays as I'd like, but I cannot figure out how to bind the selected value back to the Kendo grid. When the user clicks on the "save" button on the grid, the controller action is never invoked.
If I replace the editor template with a standard Kendo control such as a numeric text box or Kendo slider, it works fine.
ViewModel
public class LotViewModel
{
public int LotId { get; set; }
[Display(Name = "Level")]
[Range(1, 2)]
[UIHint("LotLevel")]
public int Level { get; set; }
}
View
#(Html.Kendo().Grid<LotViewModel>()
.Name("lotGrid")
.Columns(columns =>
{
columns.Bound(x => x.LotId).Visible(false);
columns.Bound(x => x.Level);
columns.Command(command =>
{
command.Edit();
}).Width(100);
})
.ToolBar(toolbar => toolbar.Create())
.Editable(editable => editable.Mode(GridEditMode.InLine))
.AutoBind(true)
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(m => m.LotId);
model.Field(m => m.Level).DefaultValue(1);
})
.Read(update => update.Action("GetLots", "Lot"))
.Create(update => update.Action("CreateLot", "Lot"))
.Update(update => update.Action("UpdateLot", "Lot"))
)
)
EditorTemplate: LotLevel
#model int
#{
var levelOne = Model.Equals(1) ? "active btn-primary" : null;
var levelTwo = Model.Equals(2) ? "active btn-primary" : null;
var htmlField = ViewData.TemplateInfo.HtmlFieldPrefix;
}
#Html.HiddenFor(model => model)
<div class="btn-group btn-group-#htmlField">
<button type="button"
class="btn btn-default #levelOne bool-#htmlField"
onclick="javascript: setValue(this, 1);">
Level 1
</button>
<button type="button"
class="btn btn-default #levelTwo bool-#htmlField"
onclick="javascript:setValue(this, 2);">
Level 2
</button>
</div>
<script>
function setValue(button, level) {
$('.btn-group-#htmlField button.active').removeClass('active btn-primary');
$(button).addClass('active btn-primary');
$('##htmlField').val(level); // TODO: Set the value of the model here
}
</script>
It comes down to binding. The editor template is instantiated once (with an empty model object) when the grid is created and then hidden. When you click "Edit" the editor is placed into the DOM, replacing the display row, and the values in the dataSource object are bound to the inputs in the editor template (by name, I think). With standard or kendo inputs this causes the editor to update and display the correct value. With a complex editor (or a complex object) the binding essentially fails and goes no further.
In your case, you can add an event handler to the Grid's edit event that will force the button to update to the input value when the editor is shown.
I have created a DisplayWithIdFor using the following code and it works showing the information I wish it to.
public static class DisplayWithIDHelper
{
public static MvcHtmlString DisplayWithIdForApplication<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string wrapperTag = "div")
{
var id = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(ExpressionHelper.GetExpressionText(expression));
return MvcHtmlString.Create(string.Format("<{0} style=\"color: #003F51; margin-left: 87px;\" class=\"{1}\">{2}</{0}>", wrapperTag, id, helper.DisplayFor(expression)));
}
}
My problem is simple, when I use the custom helper I end up with the label saying Application and the displayfor holding the name of the application showing with no space between them. see below.
Lastly here is the code for the image above:
<form>
<fieldset>
<p>
#Html.LabelFor(model => model.changeStatus.usersName)
#Html.DisplayFor(model => model.changeStatus.usersName)
#Html.HiddenFor(model => model.changeStatus.usersName)
#Html.ValidationMessageFor(model => model.changeStatus.usersName)
</p>
<p style="display: inline; float: left">
#Html.LabelFor(model => model.changeStatus.application)
#Html.DisplayWithIdForApplication(model => model.changeStatus.application)
#Html.HiddenFor(model => model.changeStatus.application)
#Html.ValidationMessageFor(model => model.changeStatus.application)
</p>
<p>
#Html.LabelFor(model => model.changeStatus.reasons)
#Html.TextAreaFor(model => model.changeStatus.reasons, new { #cols = "80", #rows = "4", #class = "k-textbox" })
<span style="color: red;"> #Html.ValidationMessageFor(model => model.changeStatus.reasons)</span>
</p>
<!-- Allow form submission with keyboard without duplicating the dialog button -->
<input type="submit" tabindex="-1" style="position:absolute; top:-1000px">
</fieldset>
</form>
Can anyone explain how to add the spacing between the two Html Helpers?
any additional code can be supplied like the Jquery popup code.
Thank you.
Edit:
Just to make things a little clearer I have to get the application name from the row of a kendo grid that is selected and set the name in the jquery using the following code:
$("div[class='changeStatus_application']").html(applicationName);
To simplify and ensure everything is acting the same, remove the:
style="display: inline; float: left"
from the second paragraph tag and use an element like a span instead of a div (block level element) in your helper.
You may then want to alter the margin left on your DisplayWithIDHelper.
Also try using classes instead of style attributes. You can then change the look of your site through your style sheet without having to recompile plus styles are centralised; easier to maintain.
New to orchard. I'm trying to style a custom form without success. I've also been using the shape modeler.
I created a New ContentType and add Fields f1, f2, f3... I create a CustomForm. Now I want to wrap different divs around certain fields say
<div class="g1">
f1
f2
</div>
<div class="g2">
f4
f6
</div>
BTW I've tried this construct without success:
dynamic content = #Model.Content;
<div class="g1">
if(#content.ContentTypeName.f1 || #content.ContentTypeName.f2)
{ #Dispaly(...)
}
</div>
Can this be done in the content.edit.cshtml view?
If so please provide an example.
Thanks
Also, is there any way to reflect the properties of Model.Content during runtime?
What I did was create local zones in the Content_Edit alternate then rearrange the fields using placement.info
Placement.info:
<Match ContentType="MyForm">
<Place Fields_Input_Edit-FirstField="MyFirstZone:1"/>
<Place Fields_Input_Edit-SecondField="MySecondZone:1"/>
</Match>
Content.Edit-MyForm.cshtml:
<div class="edit-item">
<div class="edit-item-primary">
#if (Model.Content != null)
{
<div class="edit-item-content">
<div class="first-zone">
#Display(Model.MyFirstZone)
</div>
<div class="second-zone">
#Display(Model.MySecondZone)
</div>
</div>
}
</div>
....
</div>
Typically in Orchard, you override templates for parts and fields, not for the whole content item. Shape tracing can help you determine what template alternates you can use for each field and even generate the file with the default code for you to modify.
Also, Model.Content, if anything, will be a zone, not the content item. Depending on which template you're in, you may be able to use Model.ContentItem instead.
Finally came up with a solution, albeit quite ugly, but it works:
In my alternate associated with the Content_Edit:
#{
dynamic content = Model.Content;
}
<div class="g1">
#{
foreach (var item in content)
{
if (item.Model.GetType().Name == "BooleanField")
{
if (f.Name == "f1" || f.Name == "f2")
{
#Display(item);
}
}
}
}
</div>
<div class="g2">
#{
foreach (var item in content)
{
if (item.Model.GetType().Name == "BooleanField")
{
if (f.Name == "f4" || f.Name == "f6")
{
#Display(item);
}
}
}
}
</div>
I am very new to Orchard.
I have created a new theme, based on the Minty theme. The only real change is the layout, where I have adapted the html from an existing asp.net masterpage to match the orchard style razor layout.cshtml. I have experience with MVC and razor, so no problem on that side... unless I have missed something vital.
The problem is the login page. Clicking the sign in link takes me to the correct url without errors, but not login form gets rendered. I have checked that this is the case by Inspecting Element in google chrome.
I am aware that setting up widgets, etc, I can make content appear. However, I can't find how the login form gets inserted when the login url gets requested. I presume it uses the Orchard.Users module, but not sure how. Does it need a specific zone? I can't see why, but see how else.
As a result, I can't solve my problem...
Any pointers?
Any books or other learning media?
The code for my layout.cshtml is:
#functions {
// To support the layout classifaction below. Implementing as a razor function because we can, could otherwise be a Func<string[], string, string> in the code block following.
string CalcuClassify(string[] zoneNames, string classNamePrefix) {
var zoneCounter = 0;
var zoneNumsFilled = string.Join("", zoneNames.Select(zoneName => { ++zoneCounter; return Model[zoneName] != null ? zoneCounter.ToString() : "";}).ToArray());
return HasText(zoneNumsFilled) ? classNamePrefix + zoneNumsFilled : "";
}
}
#{
/* Global includes for the theme
***************************************************************/
SetMeta("X-UA-Compatible", "IE=edge,chrome=1");
Style.Include("http://fonts.googleapis.com/css?family=Handlee");
Style.Include("http://html5shiv.googlecode.com/svn/trunk/html5.js");
Style.Include("site.css");
Script.Require("jQuery").AtHead();
Script.Require("jQueryUI_Core").AtHead();
Script.Require("jQueryUI_Tabs").AtHead();
Script.Include("http://cdnjs.cloudflare.com/ajax/libs/modernizr/2.0.4/modernizr.min.js").AtHead();
Style.Include("TagDefaults.css");
Style.Include("LayoutStructure.css");
Style.Include("LayoutStyling.css");
Style.Include("TopMenu.css");
Style.Include("LeftBlock.css");
Style.Include("RightBlock.css");
Style.Include("MenuAdapter.css");
Style.Include("Content.css");
Style.Include("FloatedBoxes.css");
Style.Include("Helen.css");
/* Some useful shortcuts or settings
***************************************************************/
Func<dynamic, dynamic> Zone = x => Display(x); // Zone as an alias for Display to help make it obvious when we're displaying zones
/* Layout classification based on filled zones
***************************************************************/
//Add classes to the wrapper div to toggle aside widget zones on and off
var asideClass = CalcuClassify(new [] {"Sidebar"}, "aside-"); // for aside-1, aside-2 or aside-12 if any of the aside zones are filled
if (HasText(asideClass)) {
Model.Classes.Add(asideClass);
}
//Add classes to the wrapper div to toggle tripel widget zones on and off
var tripelClass = CalcuClassify(new [] {"TripelFirst", "TripelSecond", "TripelThird"}, "tripel-"); // for tripel-1, triple-2, etc. if any of the tripel zones are filled
if (HasText(tripelClass)) {
Model.Classes.Add(tripelClass);
}
//Add classes to the wrapper div to toggle quad widget zones on and off
var footerQuadClass = CalcuClassify(new [] {"FooterQuadFirst", "FooterQuadSecond", "FooterQuadThird", "FooterQuadFourth"}, "split-"); // for quad-1, quad-2, etc. if any of the quad zones are filled
if (HasText(footerQuadClass)) {
Model.Classes.Add(footerQuadClass);
}
var slideshowClass = CalcuClassify(new[] {"HomeSlideshow"}, "slideshow-");
if (HasText(slideshowClass)) {
Model.Classes.Add(slideshowClass);
}
/* Inserting some ad hoc shapes
***************************************************************/
//WorkContext.Layout.Header.Add(New.Branding(), "5"); // Site name and link to the home page
//WorkContext.Layout.Footer.Add(New.BadgeOfHonor(), "5"); // Powered by Orchard
WorkContext.Layout.Footer.Add(New.User(), "10"); // Login and dashboard links
/* Last bit of code to prep the layout wrapper
***************************************************************/
Model.Id = "layout-wrapper";
var tag = Tag(Model, "div"); // using Tag so the layout div gets the classes, id and other attributes added to the Model
}
#tag.StartElement
<a name="top"></a>
<div id="SiteHeader">
</div>
<div id="PageContainer">
<div style="position: absolute; Left:-80px; top:-88px;z-index:1000;">
<img id="bird" title="Pheasant" src="/Themes/TheFarmsBlogs/Styles/Images/PositionedImages/pheasant.gif" />
</div>
<div class="SiteMenu"><p>Hello Menu</p></div>
<div id="Specialized">
<div id="PageName">
<!--
PageName NOT in use!
-->
</div>
#if (Model.RightColumn != null) {
<div id="RightCol">
#Zone(Model.RightColumn)
</div>
}
<!-- Page divided into two main columns, of which the left column is subdivided as necessary -->
<div id="LeftCol">
<div id="PageBanner">
<div id="PageBannerLeft">
#if (Model.MainImage != null) {
<div id="PageBannerImage">
#Zone(Model.MainImage)
</div>
}
#if(Model.TheStrip != null) {
<div id="TheStrip">
#Zone(Model.TheStrip)
</div>
}
</div>
</div>
<div id="SpecializedContent">
#if(#Model.content != null)
{
#Zone(Model.content)
}
</div>
</div>
<div id="SpecializedFooter">
</div>
</div>
<div id="PageFooter">
#if (Model.FooterPage != null){
#Zone(Model.FooterPage)
}
</div>
</div>
<div id="SiteFooter">
#Display(Model.Footer)
The Farms Ltd - © 2007
</div>
#tag.EndElement
PS: the branding and badge of honour are commented out as I am only enabling bit by bit to eliminate the source of errors. It will be in the live site.
ADDENDUM:
See Bertrand Le Roy's answer below. The Orchard.Users module requires a Content zone with a Capital C. That instantly cured the problem.
I added this as Bertrand's response was tentative, and I wanted to reinforce that the problem was the name of the zone.
In Orchard.Users, look for Controllers/AccountController.cs. In there, there is a LogOn action. It creates a LogOn shape that it then puts in a shape result. This then gets resolved as the Views/LogOn.cshtml template (which you can override in your theme by just dropping a file with the same name in there, for example a copy of the original that you can tweak). The LogOn template will be rendered within the theme's layout, in the Content zone. Does this answer your question?
I think the mistake you made was to name your Content zone content (notice the casing).