Is there anyway to predefine what blocks should appear in a page?

Vote:
 

So I've got some pages, and the model for the page type contains some data which is likely to be displayed in most instances but I'd like user to be able to lay that out.

My thought was if I could have a block in a content area that was created as the page way then the user could insert other blocks before or after it, or if they didn't want it they could remove the block post creation.

This also fits with my standard page from which these page types inherit, since the content areas are the same it's just for the more specific page type I have some more specific stuff in my main body content and, as I see it, some predefined blocks in my right column.

Unfortunately I don't have any eperience of programmatically creating pages or blocks, let alone then programmatically adding them to content areas so it would be good to know if firstly this is the right approach, and secondly how I might go about doing it.

#86474
May 22, 2014 12:03
Vote:
 

Hi Mark,

I would override the SetDefaultValues and in here create a block and then put it into your content area.

Something like 

public override void SetDefaultValues(ContentType contentType)
{
	base.SetDefaultValues(contentType);
	var sharedBlock = ServiceLocator.Current.GetInstance<IContentRepository>().GetDefault<MyBlock>(ContentReference.GlobalBlockFolder);
	.. add values to props on block ..
	MainContentArea = new ContentArea();
	MainContentArea.Add(sharedBlock);
}
#86476
May 22, 2014 12:24
Vote:
 

Oh.. the Add method is obsolete as of EPiServer 7.5 so if your on 7.5 or higher you need to add a ContentAreaItem to the ContentArea's Items property instead.

public override void SetDefaultValues(ContentType contentType)
{
	base.SetDefaultValues(contentType);
	var repository =ServiceLocator.Current.GetInstance<IContentRepository>();
	var sharedBlock = repository.GetDefault<MyBlock>(ContentReference.GlobalBlockFolder);
	.. add props to block ..
	var savedReference = repository.Save(sharedBlock, DataAccess.SaveAction.Publish);
	MainContentArea = new ContentArea();
	MainContentArea.Add(savedReference); or MainContentArea.Items.Add(savedReference);
}

 

More info:

http://world.episerver.com/Blogs/Johan-Bjornfot/Dates1/2012/11/Shared-blocks--IContent/

http://joelabrahamsson.com/working-programmatically-with-local-blocks-in-episerver-7/

#86477
Edited, May 22, 2014 12:27
Vote:
 
public override void SetDefaultValues(ContentType contentType)
{
	base.SetDefaultValues(contentType);
	var repository =ServiceLocator.Current.GetInstance<IContentRepository>();
	var sharedBlock = repository.GetDefault<MyBlock>(ContentReference.GlobalBlockFolder);
	.. add props to block ..
	var savedReference = repository.Save(sharedBlock, DataAccess.SaveAction.Publish);
	MainContentArea = new ContentArea();
	MainContentArea.Add(savedReference); or MainContentArea.Items.Add(savedReference);
}


Sorry for spamming but seems the editing button  doesn't work with code blocks.

#86478
May 22, 2014 12:32
Vote:
 

Tacking the block creation firstly - I am able to get the following to create a block:

public override void SetDefaultValues(ContentType contentType)
{
    base.SetDefaultValues(contentType);
 
    var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
 
    IContent block = repository.GetDefault<AuthorBlock>(ContentReference.GlobalBlockFolder) as IContent;
    block.Name = "Author";
 
    var reference = repository.Save(block as IContentSaveAction.Publish);
}

However, this seems to get called twice creating 2 blocks, also if possible I'd like to scope the blocks to the 'For this page' block folder. As such I'm wondering if default values is the right place or if there is another event I need look at.

#86485
May 22, 2014 14:38
Vote:
 

I've moved this into an OnPageCreated method through an initializable module, so now I have:

[InitializableModule]
public class PageInitialization : IInitializableModule
{
    public void Initialize(EPiServer.Framework.Initialization.InitializationEngine context)
    {
        DataFactory.Instance.CreatedPage += CreatedPage;
    }
 
    public void Preload(string[] parameters)
    {
        throw new NotImplementedException();
    }
 
    public void Uninitialize(EPiServer.Framework.Initialization.InitializationEngine context)
    {
        DataFactory.Instance.CreatedPage -= CreatedPage;
    }
 
    protected void CreatedPage(object sender, PageEventArgs e)
    {
        if (e.Page is BasePageData)
        {
            (e.Page as BasePageData).OnPageCreated(e);
        }
    }
}

Then in my page type I override an OnPageCreated method inherited from my BasePageData:

public override void OnPageCreated(PageEventArgs e)
{
    var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
 
    IContent block = repository.GetDefault<AuthorBlock>(e.PageLink) as IContent;
    block.Name = "Author";
 
    var reference = repository.Save(block as IContentSaveAction.Publish);
}

But still haven't figured out how to get the block in the 'For this page' block folder, the page link doesn't seem to work.

#86492
May 22, 2014 15:01
Vote:
 

From the database it looks like the 'For This Block' folder doesn't exist until a block is added to it for the first time, I've been trying to figure out how it works from the UI.

From the databse the page specific block folders appear to be created as fkContentTypeID = 4 with fkParentID = 4, what actually looks to tie it to the page is that the ContentOwnerID is the ContentGUID of the page.

Interestingly these page related block folder still exist in the database event if the owner item is trashed, so I don't know whether caution is advised due to the potential for inflating the database with the orphaned items or whether a clean-up does exist for these at some point.

All that said I've still got to figure out how I can use that to get the reference to it.

#86499
May 22, 2014 15:29
Vote:
 

Interestingly I thought I could hack it, and although the code below looks like it creates the right structure in tblContent table of the database it doesn't actually get seen as the 'For This Page' block folder, and when you try add another block it creates another folder under ContentReference(4) which is then used. Shame as it is really making me wonder how I can create a page with blocks that the user can then rearrange to customise the display of the page data.

Anyway here's the code that runs fine, but doesn't work:

public override void OnPageCreated(PageEventArgs e)
{
    var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
 
    var rootAssetFolder = new ContentReference(4);
    var folders = repository.GetChildren<ContentAssetFolder>(rootAssetFolder);
 
    ContentAssetFolder folder = null;
    foreach (ContentAssetFolder item in folders)
    {
        if (item.ContentOwnerID == e.Page.ContentGuid)
        {
            folder = item;
        }
    }
    if (folder == null)
    {
        folder = repository.GetDefault<ContentAssetFolder>(rootAssetFolder);
        folder.Name = e.Page.Name;
        folder.ContentOwnerID = e.Page.ContentGuid; 
        repository.Save(folder as IContentSaveAction.Publish);
    }
 
    IContent block = repository.GetDefault<AuthorBlock>(folder.ContentLink) as IContent;
    block.Name = "Author";
 
    var reference = repository.Save(block as IContentSaveAction.Publish);
}
#86500
May 22, 2014 15:57
Vote:
 

Stumbled across a couple of things. there is a ContentAssetFolder.Attach(IContent content) method, but whenever I try call it I get a read only error on PageContentAssetID.

I get the same if I try update that on the page directly, as below.

e.Page.ContentAssetsID = folder.ContentGuid;
repository.Save(e.Page, SaveAction.Save);

I don't know if this just isn't really available or I'm somehow accessing a read-only version of the page.

#86502
May 22, 2014 16:27
Vote:
 

Found a ResolveContentFolder with any luck perhaps that creates a folder if it doesn't find one?

EPiServer.DataFactory.Instance.ResolveContentFolder(e.Page.PageFolderID)



#86504
May 22, 2014 16:35
Vote:
 

Success! Need some refactoring and testing though. My problem is that I needed a writable clone of the page to update the necessary bits.

Code to create a block on page creation and add it to the 'For This Page' asset folder is as follows:

public override void OnPageCreated(PageEventArgs e)
{
    var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
 
    var rootAssetFolder = new ContentReference(4);
    var folders = repository.GetChildren<ContentAssetFolder>(rootAssetFolder);
 
    ContentAssetFolder folder = null;
    foreach (ContentAssetFolder item in folders)
    {
        if (item.ContentOwnerID == e.Page.ContentGuid)
        {
            folder = item;
        }
    }
    if (folder == null)
    {
        var currentPage = e.Page.CreateWritableClone();
 
        folder = repository.GetDefault<ContentAssetFolder>(rootAssetFolder);
        folder.Name = e.Page.Name;
        folder.ContentOwnerID = e.Page.ContentGuid;
        folder.Attach(currentPage);
        repository.Save(folder, SaveAction.Publish);
 
        currentPage.ContentAssetsID = folder.ContentGuid;
        repository.Save(currentPage, SaveAction.Save);
    }
 
    IContent block = repository.GetDefault<AuthorBlock>(folder.ContentLink) as IContent;
    block.Name = "Author";
 
    var reference = repository.Save(block as IContentSaveAction.Publish);
}

Next step, adding it to the content area.

#86505
May 22, 2014 16:41
Vote:
 

Sweet!

Was just going to add that I found a SiteDefinition.Current.ContentAssetsRoot so you could have used that to check if a folder for the currentPage exists else create a new. But glad you got it working

#86508
May 22, 2014 16:53
Vote:
 

Will take a look at what you found for resolving the folder as I'm never too happy with hardcoded IDs, even if they are unlikely to change. :)

The last bit to add to the content area turned out to be much easier once I'd got that down, just slightly to move the delcaration for currentPage outside the if statement the added this:

currentPage.RightContentArea = this.RightContentArea ?? new ContentArea();
currentPage.RightContentArea.Items.Add(new ContentAreaItem()
    {
        ContentLink = block.ContentLink
    });
repository.Save(currentPage, SaveAction.Save);

There's more options I could preconfigure on the content area item but I think for all my bits I'm happy to defer that to the user.

#86509
May 22, 2014 16:59
Vote:
 

I switched some of my logic to tidy it up, and put the logic around content asset folders in an extension method. Thought I'd share it below.

Now I have the extension method:

...

public static ContentAssetFolder GetContentAssetFolder(this PageData page)
{
    var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
 
    var rootAssetFolder = SiteDefinition.Current.ContentAssetsRoot; // should be id 4
    var assetFolders = repository.GetChildren<ContentAssetFolder>(rootAssetFolder);
 
    ContentAssetFolder assetFolder = null;
    foreach (ContentAssetFolder folder in assetFolders)
    {
        if (folder.ContentOwnerID == page.ContentGuid)
        {
            assetFolder = folder;
            break;
        }
    }
    
    if (assetFolder == null)
    {
        assetFolder = repository.GetDefault<ContentAssetFolder>(rootAssetFolder);
        assetFolder.Name = page.Name;
        assetFolder.ContentOwnerID = page.ContentGuid;
        assetFolder.Attach(page);
        repository.Save(assetFolder, SaveAction.Publish);
    }
 
    return assetFolder;
}

...

Then in my blog list page model I use the OnPageCreated method, which I add to my BasePageData and wired up through the intialization module earlier, to create blocks in the page:

...

public override void OnPageCreated(EPiServer.PageEventArgs e)
{
    base.OnPageCreated(e);
 
    var page = e.Page.CreateWritableClone() as BlogListingPage;
    var assetFolder = page.GetContentAssetFolder();
 
    var repository = ServiceLocator.Current.GetInstance<IContentRepository>();
 
    var pageListArchiveBlock = repository.GetDefault<PageListArchiveBlock>(assetFolder.ContentLink);
    pageListArchiveBlock.Title =
    ((IContent)pageListArchiveBlock).Name = "Blog Archive";
    var pageListArchiveReference = repository.Save(pageListArchiveBlock as IContentSaveAction.Publish);
 
    var pageListTagBlock = repository.GetDefault<PageListTagBlock>(assetFolder.ContentLink);
    pageListTagBlock.Title =
    ((IContent)pageListTagBlock).Name = "Tags";
    var pageListTagReference = repository.Save(pageListTagBlock as IContentSaveAction.Publish);
 
    page.RightContentArea = this.RightContentArea ?? new ContentArea();
    page.RightContentArea.Items.Add(new ContentAreaItem()
    {
        ContentLink = pageListArchiveReference
    });
    page.RightContentArea.Items.Add(new ContentAreaItem()
    {
        ContentLink = pageListTagReference
    });
    repository.Save(page, SaveAction.Save);
}

...

#86958
Jun 05, 2014 11:03
Vote:
 

Nice!

Thanks for sharing

#86963
Jun 05, 2014 11:10
Vote:
 

Hi,

Fyi, there is a ContentAssetHelper service you can use:

var contentAssetHelper = ServiceLocator.Current.GetInstance<ContentAssetHelper>();
ContentAssetFolder folder = contentAssetHelper.GetOrCreateAssetFolder(page.ContentLink);



#86980
Jun 05, 2014 14:56
Vote:
 

Thanks, that's good to know, I can throw my code away now. Shame I can't get those 2 hours back spent figuring it out. :/

#86982
Jun 05, 2014 15:10
Vote:
 

I have used your approach to automatically create blocks and add them to a content area for specific page types in a couple of scenarios. I think it's a good approach.

#86983
Jun 05, 2014 15:55
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.