Sunday, 13 November 2011

RedDot: Advanced Page Defintions

Page Definitions are great but what if we could expand them to allow the use of page references and even inheritance? Here's one approach to the challenge.
  
Background...
I love users but sometimes its best to keep things simple for them. Options are the enemy especially if they really don't use the CMS very often. Too many options can lead to mistakes and a general mess which requires someone to mop up and the users confidence is dented.

A good way to keep things simple is by using template pre-assignments and page definitions. I have seen many projects use this approach successfully, but, it can lead to multiple (but very similar) base templates which can become a chore to maintain with endless duplicate page definitions cascading off each.






Problem...
With these thoughts in mind, I started putting a new site together based upon the wonderfully simple 960grid css framework. Determined not to have multiple base templates but to still keep things simple for the user I experimented with page definitions a little deeper. The main problem I ran into was that you cannot assign references to page definitions.



Solution...
I'm not going to ramble on about why you would want or need to do this (it would take too long and I'm sure you will have your own opinions about it) we'll just move on to the how part...
With some basic jquery and knowledge of RQL you can achieve simple page referencing and much more...

Firstly you'll need an initialisation template for each reference (you assign this to the page definition in the place you want the actual reference to appear), here's one of mine:

<!IoRangeRedDotMode>
<script type="text/javascript">
    if (!rqlCommands)
    {
        var rqlCommands = [];
    }

    head(function() {
    var params = {  "loginGuid": "<%infLoginGuid%>", 
                    "sessionKey": "<%infSessionKey%>", 
                    "pageGuid": "<%!! Context:Pages.GetPage(Guid:<%infPageGuid%>).GetElementByName(conLeftNav).Value[Int32:0].Id  !!%>",
                    "linkGuid": "<%!! Context:Pages.GetPage(Guid:<%infPageGuid%>).MainLink.Id !!%>"
                 };
        var rqlCommand = { "SetReference": params };
        rqlCommands.push(rqlCommand);

        params = {  "loginGuid": "<%infLoginGuid%>", 
                        "sessionKey": "<%infSessionKey%>", 
                        "pageGuid": "<%infPageGuid%>"
                     };
        rqlCommand = { "Delete": params };
        rqlCommands.push(rqlCommand);
    });
</script>
<!/IoRangeRedDotMode>


So what's it doing?
Two commands are being formed here and added to an array (There maybe several of these initialisation classes in one page all adding commands to the array).
The first is defining parameters to be used in our RQL to replace the instance of our initialisation page with a reference of the actual page we want.
The second is to delete our initialisation instance so it doesn't hang around in unconnected pages.
Don't worry about the head(function()); it's there because I'm using head.js, if you're using vanilla jquery it would be $document.ready() or similar.

At the bottom of our base template we include a javascript file to process the queue of commands. Here I'm using an AJAX (or SJAX to be more precise) call to a custom web service which in turn calls my RustyLogic RedDotNet library, you could of course call the RQL functions directly in javascript or any other language you choose.

Here is my version:

<script type="text/javascript">

<!IoRangeRedDotMode>
    head(function () {
    if (!(typeof rqlCommands === "undefined")) {
            while (rqlCommands.length > 0) {
                $('#loading').replaceWith('<div id="loading" class="loading" style="width: 100%; height: 100%; top: 100px; display: inline-block; position:absolute; text-align: center; z-index:10000;"><p><img src="/cms/WebClient/App_Themes/Standard/Images/spinner.gif" />Please wait while page is initialised...' + rqlCommands.length + '</p></div>');
                var rqlCommand = rqlCommands.pop();
                ExecuteRql(rqlCommand);
            }
            location.reload();
        }
    });
<!/IoRangeRedDotMode>


function ExecuteRql(rqlCommand)
{
            $.each(rqlCommand, function (commandName, commandParameters) {

                    $.ajax({
                        type: "POST",
                        async: false,
                        contentType: "application/json; charset=utf-8",
                        data: JSON.stringify(commandParameters),
                        url: "/services/RustyLogic.asmx/" + commandName,
                        dataType: "json",
                        
                        success: function (response) 
                        {
                        },
                        error: function (response) 
                        {
                        },
                        complete: function (response) 
                        {
                        }
                    });
            });
}


</script>
 
When a new page is created in SmartEdit the queue is built and executed followed by a page refresh to show the changes to the user.

It's a simple example of what can be achieved but opens up many possibilities. It is easily expanded to allow inheritance from parent pages (I am using it to inherit top navigation when a child page is created).
This has resulted in a simple base class which can be used throughout the site and automation of page initialisation which means less for the user to worry about.

I hope you have enjoyed my first blog post,

John









6 comments:

  1. Hi John,

    this is realy a great approach! Thank you so much for sharing.

    Would it be possible to share also the RustyLogic.asmx file? Because I like your .Net wrapper a lot!

    Thanks
    Burkhard Pauli

    ReplyDelete
  2. Glad you found the post inspiring. I will be posting an updated .Net wrapper library including the asmx in the near future.

    ReplyDelete
  3. Hey John, just wanted to confirm that we are using a VERY similar approach to our projects. We've been waiting and waiting on improved page definitions but before we could wait we started using just pure JS to call the RQL Web-service directly for page initializations.

    I like this solution a lot and any real hardcore RedDot developer should consider this approach to make their projects much more usable for the authors. With initializations we were actually able to reduce a little bit of pre-execute and dynamic code in our projects which makes SmartEdit interface faster for authors too.

    Great post, I'll be looking into your blog more often.

    ReplyDelete
  4. Hi Arek,
    Pure JS would certainly be faster and kudos to you for implementing it. Seeing as I'm only running it at the page creation stage I thought I could get away with a slightly slower approach.

    ReplyDelete
  5. Hey John,

    How have you liked using 960gs in CMS? To what extent are you using it? I've been thinking of giving authors the power to move content around if I could find a suitable application, maybe a gallery or something.

    Cheers :)

    ReplyDelete
  6. Hi Allan, we have some 1, 2 and 3 column templates which users can add to pages (more likely they will use pre-defined page definitions). Each column can have different content classes added inside them. It's hard to explain really, but not so dissimilar to the best practice project (except that one is based on a fluid layout).

    ReplyDelete

Note: only a member of this blog may post a comment.