Where is the code that creates the Layout shape in Orchard CMS? - orchardcms

We are trying to understand the Orchard request life cycle by reading this blog post by Bertrand Le Roy. We were able to step through the code and to create a diagram of paragraph one, which describes the creation of a Shape for a specific route. Good.
Sequence Diagram of Paragraph One
Relevant Code from Paragraph One
public ActionResult Display(int id) {
var contentItem =
_contentManager.Get(id, VersionOptions.Published);
if (contentItem == null)
return HttpNotFound();
if (!Services.Authorizer.Authorize(Permissions.ViewContent,
contentItem,
T("Cannot view content"))) {
return new HttpUnauthorizedResult();
}
dynamic model = _contentManager.BuildDisplay(contentItem);
return new ShapeResult(this, model);
}
In Paragraph Three We Get Stuck
In the third paragraph of his post, Bertrand talks about the Layout shape.
One very important shape already exists at this point on the work
context, and that is the Layout shape.
OK. So it already exists. When was it created, and where is the code that creates it?

Betrand Le Roy answered in a codeplex discussion.
[The Layout shape is] created by the work context the first time it's needed (look at
the Layout property accessor there).
His answer refers to this code:
public dynamic Layout {
get { return GetState<object>("Layout"); }
set { SetState("Layout", value); }
}

Related

How to create layout elements in Orchard 1.9

Can someone please guide me on how to create layout elements in Orchard 1.9. I couldn't find any resource online.
In general, creating a new layout element is similar to creating a new part. There is a driver and a few views involved in the process. To the point - you need to implement as follows:
An element class.. Class that inherits from Element, which contains all the element data. A model, so to speak.
A driver. Class that inherits from ElementDriver<TElement>, where TElement is the type you created above. Each element has it's own driver that handles displaying admin editor (and the postback) and frontend display views.
Shapes. All shapes should be placed under /Views/Elements/ folder, by convention.
Display shape. Named after your element, ie. MyElement.cshtml. This one renders your element on frontend.
Design display shape.. Named after your element, with .Design suffix, ie. MyElement.Design.cshtml. This one renders your element inside the layout editor.
Editor shape.. This one should be put in /Views/EditorTemplates/ folder instead. Default naming convention is Elements.MyElement.cshtml. It renders the editor shown when you drop a new element on layout editor canvas.
With all above done, your new element should appear in the list of elements on the right side of the layout editor, ready to use.
If you want to do some more complex elements, please check the existing implementations. Layouts module has a very decent architecture so you should get up to speed pretty quickly. Just keep in mind the necessary steps I wrote above.
To create a custom layout element first create a class that inherits from Element. Element is found in the Orchard.Layouts namespace so you need to add a reference. To follow Orchard standards put this file in a folder called Elements.
public class MyElement : Element
{
public override string Category
{
get { return "Content"; }
}
public string MyCustomProperty
{
get { return this.Retrieve(x => x.MyCustomProperty); }
set { this.Store(x => x.MyCustomProperty, value); }
}
}
Next, create a driver class in a folder called Drivers. This class inherits from ElementDriver<TElement> and likely you will want to override the OnBuildEditor and OnDisplaying methods. OnBuildEditor is used for handling creating our editors shape and updating our database when the editor is saved. OnDisplaying is used when we need to do things when displaying our element. Oftentimes, you will want to add properties to the shape which can be done with context.ElementShape.MyAdditionalProperty = "My Value";
public class MyElementDriver : ElementDriver<MyElement>
{
protected override EditorResult OnBuildEditor(MyElement element, ElementEditorContext context)
{
var viewModel = new MyElementEditorViewModel
{
MyCustomProperty = element.MyCustomProperty
};
var editor = context.ShapeFactory.EditorTemplate(TemplateName: "Elements.MyElement", Model: viewModel);
if (context.Updater != null)
{
context.Updater.TryUpdateModel(viewModel, context.Prefix, null, null);
element.MyCustomProperty = viewModel.MyCustomProperty;
}
return Editor(context, editor);
}
protected override void OnDisplaying(Reddit element, ElementDisplayContext context)
{
context.ElementShape.MyAdditionalProperty = "My Value";
}
}
We then just need our views. Our editor view goes into Views/EditorTemplates. The file name needs to be what we set the template name of the editor shape. In our case the view name will be Elements.MyElement.cshtml.
#model MyNameSpace.ViewModels.MyElementEditorViewModel
<fieldset>
<div>
#Html.LabelFor(m => m.MyCustomProperty, T("My Custom Property"))
#Html.TextBoxFor(m => m.MyCustomProperty, new { #class = "text medium" })
</div>
</fieldset>
Finally, we just need a view for our frontend. This view goes into the following folder Views/Elements. The name of the view file is the same as our element class name. For this example the file would be called MyElement.cshtml.
#using MyNameSpace.Elements
#using MyNameSpace.Models
#{
var element = (MyElement)Model.Element;
}
<h1>#element.MyCustomProperty</h1>
You will then have a new element that you can drag into your layout with the layout editor.
For more details on creating an element from start to finish check out my blog post on creating a Reddit element.

Welding a ContentPart having a ContentField

I'm trying to Weld my custom ContentPart SitesPart containing a ContentField of type TaxonomyField but it is not working for me. When i attach this part from UI it works perfectly fine and i see the TaxonomyField in edit mode as well as in display mode.
Following is the Activating method of my ContentHandler.
protected override void Activating(ActivatingContentContext context)
{
if (context.ContentType == "Page")
{
context.Builder.Weld<SitesPart>();
}
}
I tried to go deep into the Weld function and found out that it is not able to find correct typePartDefinition. It goes inside the condition if (typePartDefinition == null) which creates an empty typePartDefinition with no existing ContentFields etc.
// obtain the type definition for the part
var typePartDefinition = _definition.Parts.FirstOrDefault(p => p.PartDefinition.Name == partName);
if (typePartDefinition == null) {
// If the content item's type definition does not define the part; use an empty type definition.
typePartDefinition =
new ContentTypePartDefinition(
new ContentPartDefinition(partName),
new SettingsDictionary());
}
I would be highly thankful for any guidance.
Oh, you are totally right, the part is welded but if there are some content fields, they are not welded. The ContentItemBuilder try to retrieve the part definition through the content type definition on which we want to add the part. So, because it's not possible, a new content part is created but with an empty collection of ContentPartFieldDefinition...
I think that the ContentItemBuilder would need to inject in its constructor and use a ContentPartDefinition or more generally an IContentDefinitionManager... But, for a quick workaround I've tried the following that works
In ContentItemBuilder.cs, replace this
public ContentItemBuilder Weld<TPart>()...
With
public ContentItemBuilder Weld<TPart>(ContentPartDefinition contentPartDefinition = null)...
And this
new ContentPartDefinition(partName),
With
contentPartDefinition ?? new ContentPartDefinition(partName),
And in you part handler, inject an IContentDefinitionManager and use this
protected override void Activating(ActivatingContentContext context) {
if (context.ContentType == "TypeTest") {
var contentPartDefinition = _contentDefinitionManager.GetPartDefinition(typeof(FruitPart).Name);
context.Builder.Weld<FruitPart>(contentPartDefinition);
}
}
Best
To attach a content part to a content type on the fly, you can use this in your handler
Filters.Add(new ActivatingFilter<YourContentPart>("YourContentType"));
There are many examples in the source code
Best

Implementing List.cshtml for Projection throws 'Orchard.ContentManagement.ContentItem' does not contain a definition for 'TagsPart'

I have implemented List.cshtml to provide a custom display for an image gallery. This is the first time I have tried to override a Projection with a Template and at first it seemed to work fine. Then I noticed that when I try to access the Projection on the backend Orchard 1.7 falls over with:
RuntimeBinderException 'Orchard.ContentManagement.ContentItem' does
not contain a definition for 'TagsPart'
Here is some code from the template List.cshtml:
List<TagRecord> uniqueTags = new List<TagRecord>();
List<dynamic> items = Model.Items;
if (items != null && items.Any())
{
foreach (var item in items)
{
if (item != null && item.ContentItem != null)
{
TagsPart part = item.ContentItem.TagsPart;
if (part != null && part.CurrentTags != null)
{
foreach (var t in part.CurrentTags)
{
if (!uniqueTags.Contains(t))
{
uniqueTags.Add(t);
}
}
}
}
}
I am ignorant on a couple of points, which I suspect may be causing the error:
How to specify a template for a Projection (more specific than 'List.cshtml'). Can I use Placement.info? How?
How should I test for the presence of a specific part in the ContentItem? Just assigning TagsPart part = item.ContentItem.TagsPart; throws the exception above.
UPDATE: I had implemented this as a Module; that is, the List.cshtml was in the Views folder of a simple Module. If I move List.cshtml to the Theme then the problem goes away. However, I would still prefer to use a module so that the layout is independent of the theme.
Orchard 1.7 includes a new query layout provider called 'Shape'. I simply used this provider, gave it a Shape Type of 'LightboxIsotope', and created a view called 'LightboxIsotope.cshtml'.
In a projection you can customize the html rendered for each property on the List. In order to acomplish this you need to go to your query, and add a new Layout, choose the properties you need, and set everything you want.
If the layouts provided in the Queries Module donĀ“t fullfill your requirements, you can allways create your own layout provider, this blog post shows an example:
http://www.stevetaylor.me.uk/image-carousel-using-twitter-bootstrap-and-orchard-cms-projections

Orchard CMS: Do I have to add a new layer for each page when the specific content for each page is spread in different columns?

Lets say I want a different main image for each page, situated above the page title. Also, I need to place page specific images in the left bar, and page specific text in the right bar. In the right and left bars, I also want layer specific content.
I can't see how I can achieve this without creating a layer for each and every page in the site, but then I end up with a glut of layers that only serve one page which seems too complex.
What am I missing?
If there is a way of doing this using Content parts, it would be great if you can point me at tutorials, blogs, videos to help get my head round the issue.
NOTE:
Sitefinity does this sort of thing well, but I find Orchard much simpler for creating module, as well as the fact that it is MVC which I find much easier.
Orchard is free, I understand (and appreciate) that. Just hoping that as the product evolves this kind of thing will be easier?
In other words, I'm hoping for the best of all worlds...
There is a feature in the works for 1.5 to make that easier, but in the meantime, you can already get this to work quite easily with just a little bit of code. You should first add the fields that you need to your content type. Then, you are going to send them to top-level layout zones using placement. Out of the box, placement only targets local content zones, but this is what we can work around with a bit of code by Pete Hurst, a.k.a. randompete. Here's the code:
ZoneProxyBehavior.cs:
=====================
using System;
using System.Collections.Generic;
using System.Linq;
using ClaySharp;
using ClaySharp.Behaviors;
using Orchard.Environment.Extensions;
namespace Downplay.Origami.ZoneProxy.Shapes {
[OrchardFeature("Downplay.Origami.ZoneProxy")]
public class ZoneProxyBehavior : ClayBehavior {
public IDictionary<string, Func<dynamic>> Proxies { get; set; }
public ZoneProxyBehavior(IDictionary<string, Func<dynamic>> proxies) {
Proxies = proxies;
}
public override object GetMember(Func<object> proceed, object self, string name) {
if (name == "Zones") {
return ClayActivator.CreateInstance(new IClayBehavior[] {
new InterfaceProxyBehavior(),
new ZonesProxyBehavior(()=>proceed(), Proxies, self)
});
}
// Otherwise proceed to other behaviours, including the original ZoneHoldingBehavior
return proceed();
}
public class ZonesProxyBehavior : ClayBehavior {
private readonly Func<dynamic> _zonesActivator;
private readonly IDictionary<string, Func<dynamic>> _proxies;
private object _parent;
public ZonesProxyBehavior(Func<dynamic> zonesActivator, IDictionary<string, Func<dynamic>> proxies, object self) {
_zonesActivator = zonesActivator;
_proxies = proxies;
_parent = self;
}
public override object GetIndex(Func<object> proceed, object self, IEnumerable<object> keys) {
var keyList = keys.ToList();
var count = keyList.Count();
if (count == 1) {
// Here's the new bit
var key = System.Convert.ToString(keyList.Single());
// Check for the proxy symbol
if (key.Contains("#")) {
// Find the proxy!
var split = key.Split('#');
// Access the proxy shape
return _proxies[split[0]]()
// Find the right zone on it
.Zones[split[1]];
}
// Otherwise, defer to the ZonesBehavior activator, which we made available
// This will always return a ZoneOnDemandBehavior for the local shape
return _zonesActivator()[key];
}
return proceed();
}
public override object GetMember(Func<object> proceed, object self, string name) {
// This is rarely called (shape.Zones.ZoneName - normally you'd just use shape.ZoneName)
// But we can handle it easily also by deference to the ZonesBehavior activator
return _zonesActivator()[name];
}
}
}
}
And:
ZoneShapes.cs:
==============
using System;
using System.Collections.Generic;
using Orchard.DisplayManagement.Descriptors;
using Orchard;
using Orchard.Environment.Extensions;
namespace Downplay.Origami.ZoneProxy.Shapes {
[OrchardFeature("Downplay.Origami.ZoneProxy")]
public class ZoneShapes : IShapeTableProvider {
private readonly IWorkContextAccessor _workContextAccessor;
public ZoneShapes(IWorkContextAccessor workContextAccessor) {
_workContextAccessor = workContextAccessor;
}
public void Discover(ShapeTableBuilder builder) {
builder.Describe("Content")
.OnCreating(creating => creating.Behaviors.Add(
new ZoneProxyBehavior(
new Dictionary<string, Func<dynamic>> { { "Layout", () => _workContextAccessor.GetContext().Layout } })));
}
}
}
With this, you will be able to address top-level layout zones using Layout# in front of the zone name you want to address, for example Layout#BeforeContent:1.
ADDENDUM:
I have used Bertrand Le Roy's code (make that Pete Hurst's code) and created a module with it, then added 3 content parts that are all copies of the bodypart in Core/Common.
In the same module I have created a ContentType and added my three custom ContentParts to it, plus autoroute and bodypart and tags, etc, everything to make it just like the Orchard Pages ContentType, only with more Parts, each with their own shape.
I have called my ContentType a View.
So you can now create pages for your site using Views. You then use the ZoneProxy to shunt the custom ContentPart shapes (Parts_MainImage, Parts_RightContent, Parts_LeftContent) into whatever Zones I need them in. And job done.
Not quite Sitefinity, but as Bill would say, Good enough.
The reason you have to create your own ContentParts that copy BodyPart instead of just using a TextField, is that all TextFields have the same Shape, so if you use ZoneProxy to place them, they all end up in the same Zone. Ie, you build the custom ContentParts JUST so that you get the Shapes. Cos it is the shapes that you place with the ZoneProxy code.
Once I have tested this, I will upload it as a module onto the Orchard Gallery. It will be called Wingspan.Views.
I am away on holiday until 12th June 2012, so don't expect it before the end of the month.
But essentially, with Pete Hurst's code, that is how I have solved my problem.
EDIT:
I could have got the same results by just creating the three content parts (LeftContent, RightContent, MainImage, etc), or whatever content parts are needed, and then adding them to the Page content type.
That way, you only add what is needed.
However, there is some advantage in having a standard ContentType that can be just used out of the box.
Using placement (Placement.info file) you could use the MainImage content part for a footer, for example. Ie, the names should probably be part 1, part 2, etc.
None of this would be necessary if there was a way of giving the shape produced by the TextField a custom name. That way, you could add as may TextFields as you liked, and then place them using the ZoneProxy code. I'm not sure if this would be possible.

Backbone.js: pass model to sub-view by reference

UPDATE: As it turns out, i had a leftover this.model = new MasterModel(); in my subViews initialize() function.
I am trying to separate my huge view to smaller views and so I have created a "master" layout view that attaches to itself some subviews and passes it's model to them.
However, it seems that when my sub-view updates the model, these changes are not reflected on the "master" view's model.
Here's what I am trying to do:
var master = new MasterModel();
var masterView = new MasterView({model:master});
Inside of the masterView initialize() function I do this:
function: initialize() {
this.subView = new subView({model:this.model});
}
And the code that changes the model in subView is this:
function: setCurrency() {
this.model.set({ currency: this.$('.currency').val() });
}
Maybe I am doing something completely wrong here?
How many things have class "currency" are on your page?
I don't think this.$('.currency').val() means what you think it means. I think you're wanting something like $(this.el).find('.currency').val() (are you using 0.9.1? then you could shorten that to $el.find('.currency').val() ). What you have will always grab the first item on the page with class "currency". this.$ is just a convenience reference to what would normally be the global Zepto or jQuery object. Hence, my question.
Edit: awaiting response to clarification question.
Make your model global so instead of:
var master = new MasterModel();
use
window.master = new MasterModel();
and then pas this to your subViews
function: initialize() {
this.subView = new subView({model:window.master});
}

Resources