Piranha CMS HtmlRegion within Custom Region not saving content - asp.net-mvc-5

I've created a couple of custom regions within my Piranha CMS Installation but am having problems when I have any kind of text region within my custom region. The Editor will display and you can enter text but it doesn't save to the DB.
Here's my classes
using System;
using System.ComponentModel.Composition;
using Piranha.Extend;
using Piranha.Extend.Regions;
namespace MatchtechGroup.Models.Regions
{
[Export(typeof(IExtension))]
[ExportMetadata("InternalId", "SimpleTab")]
[ExportMetadata("Name", "Simple Tab")]
[ExportMetadata("Type", ExtensionType.Region)]
[Serializable]
public class SimpleTab : Extension, ITab
{
public string Title { get; set; }
public HtmlRegion Tab { get; set; }
public SimpleTab()
{
Tab = new HtmlRegion();
}
}
}
And my Manager template in Areas/Manager/Views/Extensions
#model MatchtechGroup.Models.Regions.SimpleTab
#{
Layout = "";
}
<ul class="form">
<li>
#Html.LabelFor(m => m.Title)
<div class="input">#Html.TextBoxFor(m => m.Title)</div>
</li>
<li>
#Html.TextAreaFor(m => m.Tab, new { #class = "editor", #rows = 10 })
</li>
</ul>
The manager interface renders my new region correctly in the page editor but will not save content from the Html Region. There are no errors displayed in the interface, I just don't get the 'This Page has saved' message bar appear or am I able to publish the page.
Any help would be much appreciated, feels like I'm missing something basic or just that I can't nest an HTML region within this custom region.
Thanks

The problem is probably that your HtmlValue gets invalidated in the Model Binder so that Model.IsValid is false. The easiest solution to your problem is to change your property to:
public class SimpleTab : ...
{
public string Title { get; set; }
public string Tab { get; set; }
}
The only difference would be when using it in the Razor markup. If you had a region of the SimpleTab type called MyTab the syntax would then be (for example):
<div>
<h3>#Model.Regions.MyTab.Title</h3>
<div class="content">
#Html.Raw(Model.Regions.MyTab.Tab)
</div>
</div>
The only difference is #Html.Raw() to make sure that the body isn't escaped.
Regards
/ HÃ¥kan

Related

Add a new menu section in admin page in Orchard CMS

I use Orchard CMS 1.10.1. I have created a content type, now I want to have a section menu in admin page to have specific links for this content type. I want it include New link and content item list for this content type.
How can i achieve this? preferably with no editing in the code.
You can't do this without code, at the same time you will need a small code chunk to achieve this, like the following code:
public class AdminMenu : INavigationProvider {
public Localizer T { get; set; }
public string MenuName {
get { return "admin"; }
}
public void GetNavigation(NavigationBuilder builder) {
builder
.Add(T("Your Content Type Display Name"), "1", menu => menu
.Action("List", "Admin", new { area = "Contents", id = "YourContentTypeName" }));
}
}

MVC5 Binding issue

I'm trying to set up some basic navigation on a web site I'm rewriting and I've run into a brick wall and don't see why this is not working. I'm doing something similar in a half dozen other places but it just ain't working.
What I want to do is if my article has a next and or previous ID I want to show a navigation bar with appropriate forward/reverse navigation arrows or whatever to allow user to navigate pages.
The ViewModel
public class NavViewModel
{
public int NextID { get; set; }
public int PreviousID { get; set; }
public string NextString { get; set; }
public string PreviousString { get; set; }
public bool SelectedMode { get; set; }
public NavViewModel() { }
}
The View
#Html.HiddenFor(model => model.NavigationViewModel.PreviousID)
#Html.HiddenFor(model => model.NavigationViewModel.NextID)
<div class="post-nav">
#if (#Model.NavigationViewModel.PreviousString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { #nvm = Model.NavigationViewModel }))
{
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.PreviousString" />
}
}
#if (#Model.NavigationViewModel.NextString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { nvm = #Model.NavigationViewModel }))
{
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.NextString" />
}
}
</div>
and the Controller
[HttpPost]
public ActionResult SinglePost(NavViewModel nvm)
{
return RedirectToAction("SinglePost", "Article", new { postID = nvm.PreviousID });
}
I've tried passing back the bool, the IDs, the ViewModel and they all come null or containing null values.
I had this code in a PartialView and because it wasn't working I moved it up a level into the calling view and it has the same result.
You have stated you want to navigate to the next and previous items so using forms and inputs and submitting to a POST method is not appropriate. Instead use a link to navigate to a GET method, passing the ID of the previous or next item.
#if (#Model.NavigationViewModel.PreviousString != null)
{
#Html.ActionLink(Model.NavigationViewModel.PreviousString, "SinglePost", "Article", new { postID = Model.NavigationViewModel.PreviousID }, null)
}
#if (#Model.NavigationViewModel.NextString != null)
{
#Html.ActionLink(Model.NavigationViewModel.NextString , "SinglePost", "Article", new { postID = Model.NavigationViewModel.NextID }, null)
}
The reason your code does not work will be obvious when you inspect the html generated for the <form> tag. Your generating an attribute nvm="YourAssembly.NavigationViewModel" (not a route value). If you used the correct overload to generate route values, which would be
using (Html.BeginForm("SinglePost", "Article", new { #nvm = Model.NavigationViewModel }))
it will still fail because it will generate something similar to (depending on you routes) action="/Article/SinglePost?nvm=YourAssembly.NavigationViewModel" so when you post back, the DefaultModelBinder will try to assign the string "YourAssembly.NavigationViewModel" to parameter nvm, but nvm is a complex object, not a string, so binding will fail.
You could make the POST method work by using
using (Html.BeginForm("SinglePost", "Article", Model.NavigationViewModel))
however this is just degrading performance by posting back unnecessary data and if your model contained properties that were complex objects or collections, it would fail anyway, so don't do it.
Finally, if you want to make the link look like a button, then style it using css.
Try to move hidden inputs into the form
<div class="post-nav">
#if (#Model.NavigationViewModel.PreviousString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { #nvm = Model.NavigationViewModel }))
{
#Html.HiddenFor(model => model.NavigationViewModel.PreviousID)
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.PreviousString" />
}
}
#if (#Model.NavigationViewModel.NextString != null)
{
using (Html.BeginForm("SinglePost", "Article", FormMethod.Post, new { nvm = #Model.NavigationViewModel }))
{
#Html.HiddenFor(model => model.NavigationViewModel.NextID)
<input type="submit" class="btn btn-default" value="#Model.NavigationViewModel.NextString" />
}
}
</div>

How to get values from assigned contentpart in Orchard CMS

I'm using Orchard CMS v1.8.x. We've added the FlexSlider module and to make it link to specific areas of our site, we've added a field called "Slide Link" to the FlexSliderPart object.
Now, that all works pretty neat. But I have absolutely no idea how to reference this new field on the front-end. The FlexSliderWidgetViewModel only contains fields for Title and ImagePath. I have no idea how to retrieve the SlideLink (and SubTitle) field:
namespace Tekno.FlexSlider.ViewModels
{
public class FlexSliderWidgetViewModel
{
public string Title { get; set; }
public string ImagePath { get; set; }
}
}
And my View:
#using Tekno.FlexSlider.ViewModels
#{
Style.Require("FlexSlider");
Script.Require("FlexSlider");
var items = (IEnumerable<FlexSliderWidgetViewModel>)Model.SlideItems;
}
<div class="flexslider">
<ul class="slides">
#foreach (var item in items)
{
<li>
#if (item.ImagePath != "")
{
<img src="#Display.ResizeMediaUrl(Path: item.ImagePath, Height: 400, Mode: "crop")" />
}
<span class="slide-text">#item.Title</span>
</li>
}
</ul>
<span class="slide-text-bottom"></span>
</div>
The Driver's Display function:
protected override DriverResult Display(FlexSliderWidgetPart part, string displayType, dynamic shapeHelper)
{
var items = _contentManager.Query<FlexSliderPart, FlexSliderPartRecord>("FlexSlider")
.Where(i => i.GroupId == part.GroupId).OrderBy(i => i.Sort)
.List()
.Select(i => new FlexSliderWidgetViewModel()
{
ImagePath = ((MediaLibraryPickerField)i.Fields.Single(f => f.Name == "Picture"))
.MediaParts
.FirstOrDefault() == null ? "" : ((MediaLibraryPickerField)i.Fields.Single(f => f.Name == "Picture")).MediaParts.First().MediaUrl,
Title = i.Get<TitlePart>().Title
});
return ContentShape("Parts_FlexSliderWidget",
() => shapeHelper.Parts_FlexSliderWidget(SlideItems: items));
}
I had a quick look at the code and you won't be able to access those fields in the way the module is currently working. Basically it is accessing the content item in the drivers display method and then creating a View Model with the two bits of data (title and image)it deems necessary to send to the view. I would recommend changing the drivers Display method to send back the entire content item instead of that view model, you can then access fields you attach from the view directly.
If you don't have access to the driver you could I suppose grab the content manager in your view and redo all the work that driver is doing so you can access the content items. I wouldn't recommend this approach though...
This probably isn't the answer you were hoping for, sorry.
EDIT
This is basically pseudo code as I don't have access to the module to actually see if it works, but it should point you in, well, some sort of direction.
protected override DriverResult Display(FlexSliderWidgetPart part, string displayType, dynamic shapeHelper)
{
var items = _contentManager.Query<FlexSliderPart, FlexSliderPartRecord>("FlexSlider")
.Where(i => i.GroupId == part.GroupId).OrderBy(i => i.Sort)
.List();
return ContentShape("Parts_FlexSliderWidget",
() => shapeHelper.Parts_FlexSliderWidget(SlideItems: items));
}
then in your view:
#using Tekno.FlexSlider.ViewModels
#{
Style.Require("FlexSlider");
Script.Require("FlexSlider");
}
<div class="flexslider">
<ul class="slides">
#foreach (var part in Model.SlideItems)
{
dynamic item = part.ContentItem;
<li>
<img src="#Display.ResizeMediaUrl(Path: item.FlexSliderPart.MediaParts[0].MediaUrl, Height: 400, Mode: "crop")" />
<span class="slide-text">#item.TitlePart.Title</span>
</li>
}
</ul>
<span class="slide-text-bottom"></span>
</div>
Important thing here is the casting the content item to a dynamic so you can access all the fields etc. I've also never used the new Media stuff in 1.8 so... don't know if I'm accessing that correctly. I don't like it ^^

Asp.net Mvc- Check box always give unchecked on postback if disabled=true

I have a check box and on post it always shows false (unchecked) state -:
Here is the simple code -:
public ActionResult Index3()
{
var checkBoxTest = new CheckBoxTest() {Istrue = true};
return View(checkBoxTest);
}
[HttpPost]
public ActionResult Index3(CheckBoxTest checkBoxTest)
{
return View(checkBoxTest);
}
Model -:
public class CheckBoxTest
{
public bool Istrue { get; set; }
}
View -:
#using(Html.BeginForm())
{
<div>
Check Box Test:
#Html.CheckBoxFor(model=>model.Istrue,new{#disabled="disabled"})
</div>
<div>
<input type="submit" value="Submit"/>
</div>
}
Whenever I post it on server, checkbox always shows unselected state-
But if I remove -"new{#disabled="disabled"})" from attributes it works fine.
Most of the people I saw on internet have used the same technique for disabling check box - how in their case , it is working ? or may be I am making any mistake ...
If you really want to submit the value back to server in this case, you can add a hidden variable like this:
#Html.HiddenFor(model=>model.Istrue)
Simon Svensson's comment is correct and Ankit Vijay's answer provides a good work-around.
Another work-around, if you don't care that disabled check box is not submitted, is to add two lines to your HttpPost ActionResult as follows:
[HttpPost]
public ActionResult Index3(CheckBoxTest checkBoxTest)
{
ModelState.Remove("Istrue");
checkBoxTest.Istrue = true;
return View(checkBoxTest);
}
Even though the checkbox won't be checked during the post, it will be checked when the form is redisplayed.
See Rick Strahl's Web Log for a thorough explanation of why the ModelState.Remove line is needed.

Orchard CMS, Merging templates

I am developing a Widget to show Content pushes on the home page. The push model is as below.
public class PushRecord : ContentPartRecord
{
public virtual string Header { get; set; }
public virtual string Text { get; set; }
public virtual string Url { get; set; }
}
On the admin, I modified the ContentType of the Push Widget to add Media Picker Field. I would like to make a hyper link around the image with Url provided by PushPart. Npw the widget is rendered by two templates, Parts.Push.cshtml and Fields.MediaPicker-PushWidget-Image.cshtml. How do I merge these two and make my Push rendering possible? Any help is greatly appreciated.
Maybe try suppressing display of mediapickerfield via placement.info, and then explicitly render the image with the hyperlink from the .cshtml of the PushPart. You can access the MediaPickerField url like this:
#{
var pushPart = Model.ContentPart;
var photoUrl = pushPart.MediaPickerFieldName.Url;
}
<img src="#photoUrl" ... />

Resources