Single page design using Orchard CMS
Asked Answered
C

3

5

I have a client who want's a single page design for his site where the content for each "page" is shown/hidden using javascript as the user navigates the site.

I'm not sure on the best way to approach this using Orchard. One option would be to have the content all on a single page content item but then you lose the ability to use the navigation features of Orchard and can't let the client think about administration in terms of pages.

Does anyone have ideas or experiences on how best to set this up in Orchard CMS?


Here's the solution I used based on Bertrand's advice:

public ActionResult Display(int id)
{
     var contentItem = _contentManager.Get(id, VersionOptions.Published);
     dynamic model = _contentManager.BuildDisplay(contentItem);
     var ctx = _workContextAccessor.GetContext();
     ctx.Layout.Metadata.Alternates.Add("Layout_Null");
     return new ShapeResult(this, model);
}

I created a new module with a controller containing the action method above. The action method takes a parameter for the content part id. The _contentManager and _workContextAccessor objects are being injected into the controller. The Layout.Null.cshtml view was created exactly like Bertrand suggested.

Clemmieclemmons answered 1/10, 2011 at 15:27 Comment(5)
That is an extremely bizarre design that seems to fly in the face of SEO and all known usability. What justifies it?Bohemianism
Looks like you are developing a mobile app, right? jQuery Mobile?Sapodilla
@BertrandLeRoy: I don't necessarily disagree. It's what the client wants (specifically they want me to implement this template: udfrance.com/dev/STUDIO8/index_black.html). The site is very light on content and SEO is not the primary purpose.Clemmieclemmons
@pszmyd: Not a jquery mobile app but a similar use case.Clemmieclemmons
Isn't returning the results of _content manager.Get exposing a security flaw given that - as far as I know - all the admin data can be retrieved using the content manager? It's just a matter of trying various values for the id until you get back the user account info or some other privileged data.Heartfelt
B
8

Here's what I would do to achieve that sort of very polished experience without sacrificing SEO, client performance and maintainability: still create the site "classically" as a set of pages, blog posts, etc., with their own URLs. It's the home page layout that should then be different and bring the contents of those other pages using Ajax calls. One method that I've been using to display the same contents as a regular content item, but from an Ajax call (so without the chrome around the content, without bringing the stylesheet in, as it's already there, etc.) is to have a separate controller action that returns the contents in a "null layout":

var ctx = _workContextAccessor.GetContext();
ctx.Layout.Metadata.Alternates.Add("Layout_Null");
return new ShapeResult(this, shape);

Then, I have a Layout.Null.cshtml file in my views that looks like this:

@{
    Model.Metadata.Wrappers.Clear();
}
@Display(Model.Content)

Clearing the wrappers removes the rendering from document.cshtml, and the template itself is only rendering one zone, Content. So what gets rendered is just the contents and nothing else. Ideal to inject from an ajax call.

Does this help?

Bohemianism answered 2/10, 2011 at 22:28 Comment(7)
That sounds like a great solution. Where does the controller action that returns the contents in a "null layout" go?Clemmieclemmons
Where you want: module, theme, it doesn't matter.Bohemianism
Thanks Bertrand. I'll give that a try. Not sure how to do it off the top of my head but hopefully it will be clearer when I get in there and work through it.Clemmieclemmons
You can also do the work of adding the alternate from a a handler if that's easier.Bohemianism
I've added a module (using codegen) and in my controller action added your first code block. However, I'm unclear on where the _workContextAccessor and shape objects are coming from. Can you give me some guidance? ThanksClemmieclemmons
You need to inject an IWorkContextAccessor and get the context from there. The shape object is usually built by the content manager from your content items. There are plenty of examples of that around, in particular in the blog module.Bohemianism
Hello , I want to implement the same thing, and I have used the layoutfilter example below for null layout. However, my problem is that: how can I get all other page ids?, please helpProng
C
2

Following along the lines of Bertrand's solution, would it make more sense to implement this as a FilterProvider/IResultFilter? This way we don't have to handle the content retrieval logic. The example that Bertrand provided doesn't seem to work for List content items.

I've got something like this in my module that seems to work:

public class LayoutFilter : FilterProvider, IResultFilter {
    private readonly IWorkContextAccessor _wca;

    public LayoutFilter(IWorkContextAccessor wca) {
        _wca = wca;
    }

    public void OnResultExecuting(ResultExecutingContext filterContext) {
        var workContext = _wca.GetContext();
        var routeValues = filterContext.RouteData.Values;

        if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) {
            workContext.Layout.Metadata.Alternates.Add("Layout_Null");

        }           
    }

    public void OnResultExecuted(ResultExecutedContext filterContext) {
    }        
}
Contingence answered 25/10, 2011 at 18:57 Comment(1)
Hello Rahul,I'm implementing this for my project, but how do I get the page content from ajax ?,thanksProng
C
1

Reusing Rahul's answer with added code to answer @tuanvt's question. I'm honestly not sure what your question is but if seems like you want to access the data sent with the ajax request. If it's JSON you're sending set contentType: "application/json" on the request, JSON.stringify() it , then access it in Rahul's proposed ActionFilter by extracting it from the request stream. Hope it helps in any way.

public class LayoutFilter : FilterProvider, IResultFilter {
  private readonly IWorkContextAccessor _wca;

  public LayoutFilter(IWorkContextAccessor wca) {
        _wca = wca;
  }

  public void OnResultExecuting(ResultExecutingContext filterContext) {
      var workContext = _wca.GetContext();
      var routeValues = filterContext.RouteData.Values;

      if (filterContext.RequestContext.HttpContext.Request.IsAjaxRequest()) {
           workContext.Layout.Metadata.Alternates.Add("Layout_Null");

           if (filterContext.HttpContext.Request.ContentType.ToLower().Contains("application/json"))
           {
                var bytes = new byte[filterContext.HttpContext.Request.InputStream.Length];
               filterContext.HttpContext.Request.InputStream.Read(bytes, 0, bytes.Length);
               filterContext.HttpContext.Request.InputStream.Position = 0;
               var json = Encoding.UTF8.GetString(bytes);
               var jsonObject = JObject.Parse(json);
               // access jsonObject data from ajax request
           }
      }           
  }

  public void OnResultExecuted(ResultExecutedContext filterContext) {
  }        
}
Cucullate answered 20/3, 2013 at 0:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.