Adding Layout Part to Custom Site Settings

Topics: Customizing Orchard
Sep 4, 2015 at 7:56 PM
I've built a simple site settings area that allows an administrator to specify a title, message, and timeout for our speedbump. These will then be sucked into a custom view to be displayed when a user hits the speedbump. Currently this is done using simple text boxes and text areas in Razor. I would like to instead allow the administrator to utilize the Layout Part as opposed to a text area for building the message. So far I have been unable to determine how this can be accomplished. I've tried adding a LayoutPart to my SettingsPart's model and setting it in the driver, but this isn't working. Any guidance on how to accomplish this would be much appreciated.
Sep 4, 2015 at 8:42 PM
Edited Sep 5, 2015 at 12:18 AM
The IDeliverable.Slides module's Slideshow element is basically what I'm trying to accomplish but as a site settings.

I ended up using a controller to handle the settings instead of using a driver. I have a shape for the title part and the layout part. Everything shows up fine but I'm having a hard time working out the post action to save the data for the layout and title. Any ideas how to save data from a shape into its part?
Developer
Sep 5, 2015 at 12:40 PM
Are you in fact using the LayoutPart, or are you creating the layout editor shape yourself via ILayoutEditorFactory? In any case, the layout editor renders a hidden field named Data that will be updated when the form is submitted. this Data field contains the serialized state of the layout editor model. To transform this data into a format that can be used to render a layout, follow these steps:
  1. Use ILayoutModelMapper to create an hierarchical list of elements.
  2. User ILayoutSerializer to serialize the hierarchical list of elements into a JSON string. This is the string you store.
Sep 8, 2015 at 9:51 PM
Edited Sep 8, 2015 at 9:59 PM
Okay, so I am using an editor shape and am able to get the response but am having a hard time using the ILayoutModelMapper which requires a DescribeElementsContext. I am not using a part and thus am a little confused on how to create the DescribeElementsContext needed by the ILayoutModelMapper. My shape is built in a separate controller action than the HttpPost action I'm trying to update the settings with.

**Got that working I think. I just created a new empty DescribeElementContext
Sep 8, 2015 at 10:01 PM
Edited Sep 8, 2015 at 10:30 PM
When I try to submit the form with content in the layout other than layout elements such as the grid and rows I get an error similar to the following:

A potentially dangerous Request.Form value was detected from the client (LayoutEditor.Data="... "html": "<p>test</p>", ...").

How can I get it to allow html content? Thank you

Edit: I found this occurs on the TryUpdateModel method. Do I need to do the update differently?

Edit 2: Also how do you render the html on the front end that was built using the layout editor?
Sep 10, 2015 at 9:24 PM
Edited Sep 10, 2015 at 9:25 PM
I don't know if this is the best method but I used the ILayoutSerializer and the IElementDisplay to build a shape which I displayed in my controller's view.

I'm still curious on getting around the dangerous request form submission. For the time being I just used the ValidateInput attribute to force no validation.
Sep 15, 2015 at 6:48 PM
Keep getting the following error when trying to install the module on an existing instance of Orchard as opposed to a clean install.

An unhandled exception has occurred and the request was terminated. Please refresh the page. If the error persists, go back
Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object. at Orchard.Layouts.Providers.ContentFieldElementHarvester.Displaying(ElementDisplayContext context) at Orchard.Layouts.Framework.Display.ElementDisplay.DisplayElement(Element element, IContent content, String displayType, IUpdateModel updater, String renderEventName, String renderEventArgs) at Orchard.Layouts.Services.LayoutEditorFactory.RenderElement(Element element, DescribeElementsContext describeContext, String displayType) at Orchard.Layouts.Services.LayoutEditorFactory.<>c__DisplayClassa.<GetConfigurationData>b__9(ElementDescriptor descriptor) at System.Linq.Enumerable.WhereSelectListIterator2.MoveNext() at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeList(JsonWriter writer, IEnumerable values, JsonArrayContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.SerializeObject(JsonWriter writer, Object value, JsonObjectContract contract, JsonProperty member, JsonContainerContract collectionContract, JsonProperty containerProperty) at Newtonsoft.Json.Serialization.JsonSerializerInternalWriter.Serialize(JsonWriter jsonWriter, Object value, Type objectType) at Newtonsoft.Json.JsonSerializer.SerializeInternal(JsonWriter jsonWriter, Object value, Type objectType) at Newtonsoft.Json.Linq.JToken.FromObjectInternal(Object o, JsonSerializer jsonSerializer) at Orchard.Layouts.Services.LayoutEditorFactory.GetConfigurationData(IContent content) at Orchard.Layouts.Services.LayoutEditorFactory.Create(String layoutData, String sessionKey, Nullable1 templateId, IContent content) at Orchard.Speedbump.Controllers.AdminController.ValidUrlPage() at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f()
Sep 15, 2015 at 6:58 PM
I have no idea why sometimes I get this error and other times I don't. I've tried installing it on three instances of Orchard one of which included the same old version of the module and all instances worked fine. But, when I tried to install the module on two instances of Orchard that were previously set up I get the above error. I'm not sure why it's even calling display when trying to create the editor template shape as I don't display the shape until the view. Any ideas would be greatly appreciated.
Developer
Sep 15, 2015 at 10:14 PM
I think I've seen this issue before, and fixed it.
Can you tell me what branch or version you're seeing this on?
Also, what module are you installing onto that instance?
Sep 17, 2015 at 6:19 PM
Edited Sep 17, 2015 at 6:24 PM
I'm trying to install the module I created for building speedbumps using layouts. Everything works fine on the development instance (1.9.1) and on various other installs. For some reason I randomly get this error when attempting to perform tasks such as importing content or enabling other modules. It appears random as to when this happens, but once it has happened there isn't a way to get it working again other than deleting the site and starting over. Everything is being done in Orchard 1.9.1

Edit: I may have isolated the problem or at least part of it. I noticed I received the error directly after enabling one of my custom element modules. I have a Flexslider element module that builds simple slider elements. It's been working fine for in all of our Orchard sites, but as soon as I enabled it the speedbump module's layout shapes no longer work and give the error described above. Any ideas?
Sep 17, 2015 at 6:33 PM
Edited Sep 17, 2015 at 6:36 PM
I've replicated the issue twice now by first installing and enabling my speedbump module which builds shapes for the layouts thusly:

Controller action:
var layoutEditor = Shape.EditorTemplate(TemplateName: "Parts.Layout", Model: new LayoutPartViewModel { LayoutEditor = _layoutEditorFactory.Create(settings.ValidURLMessage, Guid.NewGuid().ToString()) }, Prefix: null);            
            layoutEditor.Metadata.Position = "1";                          
Controller Post action:
var layoutModel = new LayoutPartViewModel();
            if (TryUpdateModel(layoutModel)) {
                //Create a new DescribeElementsContext
                var describeContext = new DescribeElementsContext { Content = settings };
                //Map the elements
                var elementInstances = _mapper.ToLayoutModel(layoutModel.LayoutEditor.Data, describeContext).ToArray();

                //Save the elements as a serialized string
                settings.ValidURLMessage = _serializer.Serialize(elementInstances);            
            }
My slider element module was built off of the media items element and tweaked to display the images using the jquery flexslider's javascript code.

Element Definition:
 public class FlexSlider : Element {
        public override string Category {
            get { return "Media"; }
        }

        public override string ToolboxIcon {
            get { return "\uf03e"; }
        }

        public IEnumerable<int> MediaItemIds {
            get { return Deserialize(this.Retrieve<string>("MediaItemIds")); }
            set { this.Store("MediaItemIds", Serialize(value)); }
        }

        public string DisplayType
        {
            get { return ElementDataHelper.Retrieve(this, x => x.DisplayType); }
            set { this.Store(x => x.DisplayType, value); }
        }

        //Settings
        public bool UseCustomSettings
        {
            get { return ElementDataHelper.Retrieve(this, x => x.UseCustomSettings); }
            set { this.Store(x => x.UseCustomSettings, value); }
        }

        public string Animation
        {
            get { return ElementDataHelper.Retrieve(this, x => x.Animation); }
            set { this.Store(x => x.Animation, value); }
        }

        public string Direction
        {
            get { return ElementDataHelper.Retrieve(this, x => x.Direction); }
            set { this.Store(x => x.Direction, value); }
        }

        public int SlideShowSpeed
        {
            get { return ElementDataHelper.Retrieve(this, x => x.SlideShowSpeed); }
            set { this.Store(x => x.SlideShowSpeed, value); }
        }

        public int AnimationSpeed
        {
            get { return ElementDataHelper.Retrieve(this, x => x.AnimationSpeed); }
            set { this.Store(x => x.AnimationSpeed, value); }
        }

        public bool Reverse
        {
            get { return ElementDataHelper.Retrieve(this, x => x.Reverse); }
            set { this.Store(x => x.Reverse, value); }
        }

        public bool Randomize
        {
            get { return ElementDataHelper.Retrieve(this, x => x.Randomize); }
            set { this.Store(x => x.Randomize, value); }
        }

        public bool ControlNav
        {
            get { return ElementDataHelper.Retrieve(this, x => x.ControlNav); }
            set { this.Store(x => x.ControlNav, value); }
        }

        public bool DirectionNav
        {
            get { return ElementDataHelper.Retrieve(this, x => x.DirectionNav); }
            set { this.Store(x => x.DirectionNav, value); }
        }

        public static string Serialize(IEnumerable<int> values) {
            return values != null ? String.Join(",", values) : "";
        }

        public static IEnumerable<int> Deserialize(string data) {
            if (String.IsNullOrWhiteSpace(data))
                return Enumerable.Empty<int>();

            var query =
                from x in data.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
                let id = XmlHelper.Parse<int?>(x)
                where id != null
                select id.Value;

            return query;
        }
    }
Edit: By the way, how do you change the label of the layout shape so it displays something like "Message" instead of "Layout"? Thank you.
Sep 22, 2015 at 9:04 PM
Edited Sep 22, 2015 at 9:05 PM
I've been playing around with things and I think it has something to do with the layout shape I've created. I tried using my speedbump module with the media item element and got nearly the same results. I was able to make it into the editor but once I selected some items and clicked save I got the error:
An unhandled exception has occurred and the request was terminated. Please refresh the page. If the error persists, go back
Object reference not set to an instance of an object.
System.NullReferenceException: Object reference not set to an instance of an object. at Orchard.Layouts.Drivers.MediaItemElementDriver.OnDisplaying(MediaItem element, ElementDisplayContext context) at Orchard.Layouts.Framework.Drivers.ElementDriver`1.Displaying(ElementDisplayContext context) at Orchard.Layouts.Framework.Display.ElementDisplay.<>c__DisplayClass16.<DisplayElement>b__15(IElementDriver driver) at Orchard.Layouts.Framework.Display.ElementDisplay.InvokeDrivers(IEnumerable`1 drivers, Action`1 driverAction) at Orchard.Layouts.Framework.Display.ElementDisplay.DisplayElement(Element element, IContent content, String displayType, IUpdateModel updater, String renderEventName, String renderEventArgs) at Orchard.Layouts.Controllers.ElementController.RenderElement(Element element, DescribeElementsContext describeContext, String displayType) at Orchard.Layouts.Controllers.ElementController.Update(ElementDataViewModel model, String session) at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary`2 parameters) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary`2 parameters) at System.Web.Mvc.Async.AsyncControllerActionInvoker.<BeginInvokeSynchronousActionMethod>b__39(IAsyncResult asyncResult, ActionInvocation innerInvokeState) at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`2.CallEndDelegate(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult) at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3d() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f() at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.<>c__DisplayClass46.<InvokeActionMethodFilterAsynchronouslyRecursive>b__3f()
It may have something to do with the way I'm setting up the describe element context since I had no idea how to set that up.
Sep 22, 2015 at 9:12 PM
The ElementDisplayContext passed into the OnDisplaying method of the elements has a null value for context.content. Any ideas why my layout shape isn't passing the content in the context? Thank you.
Sep 22, 2015 at 10:14 PM
Haha, I figured out the reason some modules break my layout shape and others don't. Some elements perform a check such as:
var contentItemIds = RemoveCurrentContentItemId(element.MediaItemIds, context.Content.Id);
which can be found in the MediaItem Element's driver code (the OnDisplaying method). When a layout is created via a dynamic shape it has no IContent associated with it (I haven't been able to assign it a content item and have it work yet). This is the reason a null reference exception is thrown (context.Content is null). It seems like either null checks should be added or a method for adding an associated IContent to the layout shape being created.

I may just be missing how to build the layout shape correctly to avoid this issue.