Reusing content from one web page in another

Topics: Community, XSLT
Jan 21, 2013 at 3:12 PM

I have a senario that I'd like some advice on, with regards to what is possible. The senario is as follows.

  1. I have a web page with a number of editable content blocks.
  2. I would like to resolve the URL of that web page (referenced from an external SQL database) into a PageId.
  3. I would like to use that PageId to query the C1 database for a specific content block from that web page.
  4. I would like to display the text from that content block on another web page.

I'm now quite comfortable with XSLT to do this sort of thing, but to build out this scenario I need to know:

  • How do I resolve the URL of a web page into a PageId?
  • How do I view the C1 database within the Functions perspective of C1, in order to select data from it to use in a new function?

Any help or advice will gratefully received.

Jan 21, 2013 at 3:23 PM

1. use PageUrls.ParseUrl(uri.LocalPath) to get a UrlData object which contais PageId

2. Page content are stores in instances of IPagePlaceholderContent which holds a reference to a PageId and PlaceHolderId. By using the PageId from step 1, you just need to query for the specific placeholders you need, and then extract content via the IPagePlaceholderContent.Content property.

Jan 21, 2013 at 3:31 PM

Thanks.

What's the namespace for those functions? I can't find them in the All Functions list. Are they part of the public functions API?

Jan 21, 2013 at 3:34 PM

Unfortunately not

  • Composite.Core.Routing.PageUrls
  • Composite.Data.Types.IPagePlaceholderContent
Jan 21, 2013 at 3:38 PM

That's a pity... it seems that these two would be useful additions to the public API.

Is there a way to expose them via the web console interface?  If not, how do you call them within another function?

Jan 22, 2013 at 11:26 AM

Prompted by the above conversation, I've added two Feature Requests to the Issue Tracker for some new public functions:

Please consider voting them up. Thanks.

Jan 22, 2013 at 12:12 PM
Edited Jan 22, 2013 at 12:12 PM

Don't get me wrong, but i don't quite follow your reasoning here. What makes you think that you can't go and use Composite.Core.Routing.PageUrls and Composite.Data.Types.IPagePlaceholderContent? Just because they are hidden from default intellisense in Visual Studio? There are plugins to turn that off.

Ie. for reusing content, just use this code

public XHtmlDocument GetContentFromPageAndPlaceHolder(Guid pageId, string placeholderId)�
{
   using (var data = new DataConnection()) 
   {
      var placeholder = data.Get<IPagePlaceholderContent>().SingleOrDefault(c => c.PageId == pageId && c.PlaceHolderId = placeholderId);
      if (placeholder != null) 
      {
          return XHtmlDocument.Parse(placeholder.Content);
      }
   }

   return null;
}
Jan 22, 2013 at 12:29 PM

Thanks burningice.

I probably should explain that I'm not a C# programmer. I have little experience in using Visual Studio. My expertise is in web design (with Expression Web) and university web management. As I mentioned before, I'm most comfortable with HTML, XML and XSLT.

I'm trying to find ways to build a new website, while reducing our Marketing department's dependence on our overworked team of IT developers (they have so much project work coming in that they can't devote a lot of time to us). I created the Feature Requests listed above, because it would help a lot if there were some new public functions to assist with content reuse.

If we do have to get our IT developers involved here, I'll pass on your code to them.

Jan 22, 2013 at 12:34 PM

It sounds like you would benefit of building yourself a little toolbox of what you need to expose to xslt. Inline C# functions are convenient for this if you don't to work outside the C1 console. Here you can paste this code as a function that can be used directly by your Marketing department.

http://docs.composite.net/ASP-NET/CSharpFunctions/Creating-Inline-C-Functions

Coordinator
Jan 22, 2013 at 2:33 PM

Hi David,

here is how you can use Pauli's code way without using Visual Studio:

  1. On the Functions perspective, C# Functions execute the "Add Inline C# Function" command
  2. In the dialog name it GetPageContent - set namespace to MyTools. Click OK
  3. Paste in the C# code shown below in the "Source" tab (delete what is there already)
  4. On the Input Parameters tab, create to items:
    1. "Page" of type DataReference<C1 Page>
    2. "Placeholder" of type string
  5. Save.
  6. Tnsert your function on a page or call it from an XSLT and you should see your content show up :)

You can change both namespace and function name - just remember to do so in both the source code and the Settings tab - they need to match up.

using System;
using System.Linq;
using Composite.Core.Xml;
using Composite.Data;
using Composite.Data.Types;

namespace MyTools
{
	public static class InlineMethodFunction
	{
		public static XhtmlDocument GetPageContent(DataReference<IPage> Page, string Placeholder)
		{
			using (DataConnection dc = new DataConnection())
			{
				Guid pageId = (Guid)Page.KeyValue;

				var placeholder = dc.Get<IPagePlaceholderContent>().SingleOrDefault( f=> f.PageId == pageId && f.PlaceHolderId == Placeholder );

				return (placeholder == null ? null : XhtmlDocument.Parse( placeholder.Content ));
			}
		}
	}
}

Jan 22, 2013 at 4:18 PM
Edited Jan 22, 2013 at 4:20 PM

Thank you both!  That method works fantastically.

I'm assuming that I can provide the PageId using another function, including one that transforms a URL (provided by an external database we use), into a PageId GUID?

Coordinator
Jan 23, 2013 at 11:28 AM

You can - the code above will give the end user a UI where they can select a page using the visual selector (which is fairly user friendly imo), but you could locate the page id through other means.

But in general I suggest you avoid using fully expanded URLs for binding things together, since a page may be moved or a Page URL Title may be changed, in which case a "hard coded" fully expanded URL won't match any more. In general you can use the type "DataReference<C1 Page>" for things and UI become friendly, while the system uses the immutable ID of pages internally to guard against renaming problems.

Jan 23, 2013 at 12:33 PM
Edited Jan 23, 2013 at 12:51 PM

Thanks Marcus.

I don't anticipate the URLs of our subject pages changing (they need to have a static location so that UCAS.com and other government agencies can link to them), which is why I reference the page URL in our separate tracking database (the one I'm going to query to work out where to copy information from).

If I switch that external tracking database to use the PageId GUID instead, is there a quick way to output the PageId on a page (either displayed within p tags, or as a hidden comment in the HTML source)?

Jan 23, 2013 at 1:37 PM
Edited Jan 23, 2013 at 2:07 PM

With the help of a most excellent C# friend and his copy of Visual Studio, got the lookup for the PageId from a URL working:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Composite.Core.Routing;
using Composite.Data;
using Composite.Data.Types;

namespace BSU.ContentReuse
{
	public static class InlineMethodFunction
	{
		public static Guid GetPageIdFromURL(string URL)
		{
			using (DataConnection dc = new DataConnection())
			{
				var PageId = PageUrls.ParseUrl(URL).PageId;
				
				return (PageId);
			}
		}
	}
}

I hope this is the right methodology.  It seems to do the job... except that I can't pass the result to the previous ContentReuse function (not the same data types). Is there a way to inject the result of the PageId lookup, into the Reuse function, without changing the input parameter type from "DataReference<C1 Page>" to "string"?

Coordinator
Jan 23, 2013 at 2:12 PM
Edited Jan 23, 2013 at 2:16 PM

Looks good - you can shorten it a bit:

 

using System;
using Composite.Core.Routing;

namespace BSU.ContentReuse
{
	public static class InlineMethodFunction
	{
		public static Guid GetPageIdFromURL(string URL)
		{
			Guid pageId = PageUrls.ParseUrl(URL).PageId;
				
			return pageId;
		}
	}
}

In case you are going the DataReference<IPage> way to communicate a page reference (instead of the Guid way), use this code:

using System;
using Composite.Core.Routing;
using Composite.Data;
using Composite.Data.Types;

namespace BSU.ContentReuse
{
    public static class InlineMethodFunction
    {
        public static DataReference<IPage> GetPageIdFromURL(string URL)
        {
            Guid pageId = PageUrls.ParseUrl(URL).PageId;
               
            return new DataReference<IPage>( pageId );
        }
    }
}

Coordinator
Jan 23, 2013 at 2:18 PM

>> If I switch that external tracking database to use the PageId GUID instead, is there a quick way to output the PageId on a page (either displayed within p tags, or as a hidden comment in the HTML source)?

You can get current page ID by calling SitemapNavigator.CurrentPageId.

>> I hope this is the right methodology.

That is the correct way, if I remember correctly, you don't have to have DataConnection around this one.

If you're planning to have multiple languages on the site, it has sense to save the language information as well.

Jan 23, 2013 at 3:27 PM
napernik wrote:

If you're planning to have multiple languages on the site, it has sense to save the language information as well.

Thanks for that. I'm glad to know that I'm doing this in the right way.

We are looking at having multiple language versions of our website. Does a page in our potential Chinese language site have same PageId GUID as it's equivalent page in the English site (assuming we've followed the translating pages instructions at http://users.composite.net/Pages/MultipleLanguages/TranslateaPage )?

Coordinator
Jan 23, 2013 at 4:05 PM

Yes - page ID's do not change with language. Here is how you can check the language of a URL:

	CultureInfo culture = PageUrls.ParseUrl(URL).LocalizationScope;

And here is how you would be explicit about what data to get:

	CultureInfo culture = new CultureInfo("en-GB");
	using (DataConnection dc = new DataConnection(culture))
	{
		// all data read via dc will be en-GB data
	}
You very seldom have to care about language though. If your do not specify a language explicitly the language of the current page request will be used and this is almost always what you want.

Jan 24, 2013 at 8:37 PM

Thanks. I'll only be cloning pages within the same language site, so I doubt I'll need the CultureInfo at the moment... but it's useful to know that it's there.

I've just spent the afternoon "in the zone", and now have the majority of the functions I wanted completed. I'm amazed at how easy it is to stack simple functions and get some complex process working.