Oh my god. It's full of code!

Using Salesforce as a CMS Part 1

Hey all,
Time for another mini saga about how to do something pretty awesome with Salesforce. This time, I’m going to be talking about how to (or at least how I did/do) use Salesforce as a content management systems (CMS). For those unaware, in simple terms a CMS allows regular non coding users to adjust the content delivered by some system. In this case, we are going to allow regular old Joe Blow to change the data on our Salesforce hosted website. Now we probably all have worked with Visualforce and know that it is fairly easy, but still not quite easy enough for someone in marketing. It’s obvious if we want those people to really be in charge of the website, we have to give them the tools to do it themselves (more time for you to play Portal 2, or something ;). So how do we do that? Well leverage a lot of Salesforce built in editing and management functions, and build a custom object or two to house our data. Then we’ll use a nice front end for HTML content editing. After that, we write (or reuse) a component to fetch the data (this site is going to be all Ajax btw ;) and some javascript to handle parsing of the content. So let’s get to it.

First off, you’ll want an object to house the actual HTML and script data that will be your site. So go ahead and create a new object, I called mine webpage (API name webpage__c). In that, object create two fields, one called title (a text field), and one called page content (a long text field). You might ask why not use use the richtext field provided by Salesforce since it has a nice HTML editing interface. The short answer is, because it blows. It’s not powerful enough, it writes garbage HTML and it seems a bit unpredictable as for how it formats its code (at least in my experience, you are welcome to try if you like).

So now you have the object that will house your website data. Later on you’ll probably want to go and put security on this, make sure only required profiles can see and interact with them, but for now to keep it simple, I’d keep it open. Now we need a place to host this data. We’ll be using Salesforce sites to present the content to the outside world. If you have a site already, then you are set. If you don’t have a site, lets make one now. Head to setup->customize->develop->sites. If you haven’t made a site, you’ll need to choose a domain name which will end up being yourDomain.force.com. Your first site can live in that root directory, and any sites you make after will be in a “subfolder” of that main one. Feel free to check out the settings and make adjustments as you see fit. One thing you’ll want to make sure, is that the profile that this site is running under has access to your webpage object. To do that, just hit the public access settings button on the detail page of the site. Find your webpage object and give it read all permission.

Ok, next we need to put some pieces in place for the website to function. We of course need a way to get our webpage content to the site. Also, because Salesforce is so on top of their shit, any HTML you write on their platform gets escaped. Meaning any < gets turned into < and so on. So we need a script that undoes that to present syntactically valid HTML. Getting the content is pretty easy, we can use my queryToJson component to do that for us. You can go download it on my projects page. Essentially each link we have, will create a javascript request that will use queryToJson to get the page content. Then that content will get fed through a parser, and then presented to the user. So go snag queryToJson and put that in your org. Also, host it through your site (both the page and the class must be available). So again, use that public access settings button on your page to enable those resources for the site.

Next, we'll need to get our parser and data fetcher together. Now you can either host this javascript directly in the visualforce page (inline) we'll create, or you can put it in an reusable component, or even in a static resource. Each have their pros and cons. I'd recommend a component. They are reusable, tweakable in prod (if you have to make emergency changes it's nice), and you don't have to download and upload them. Anyway, grab this javscript and somehow make it available to the page we'll be creating soon.

function loadPageSF(pageID)
{
    pageID=pageID.replace(/\%20/g,' ');
    var query = "page_content_new__c from webpage__c where id = '"+pageID+"'";
    query = encodeURIComponent(query);
    jQuery.getJSON('http://yourDomain.force.com/queryToJson?queryString='+query+'&callback=?', function(data) {
        try
        {
            //set the contents of the div to display the result of the data
            var encoded = data.returnStringSet[0].page_content_new__c;           
            var decoded = decodeURIComponent(encoded.replace(/\+/g,  " ")); 
            decoded=decoded.replace(/\&lt;/g,'<');
            decoded=decoded.replace(/\&gt;/g,'>');            
            decoded=decoded.replace(/\[link]/g,"<a href=" + "'javascript:loadPageSF("+'"');
            decoded=decoded.replace(/\[text]/g,'"'+")'>");
            decoded=decoded.replace(/\[\/text]/g,"</a>");
            decoded=decoded.replace(/\[\/link]/g,"");
            decoded=decoded.replace(/\[\/vflink]/g,"");  
            decoded=decoded.replace(/\[vflink]/g,"<a href=" + "'javascript:loadPageVF("+'"');                                          ;
   
            jQuery('#main-textarea').html(decoded);
         
        }
        catch(e)
        {
            console.log(e);
        }        
    });
}

Oh and Also

function loadPageVF(pageID)
{
        jQuery.ajax({
        type: "GET",
        url: "http://yourdomain.force.com/"+pageID,
        success: function(data) {
             jQuery('#main-textarea').html(parsePage(data));
             jQuery('input:text').setMask();
        }
        });    
        jQuery('#leftImage').addClass(leftBarStyles[Math.floor(Math.random()*4)]);  
}

If you are familiar with javascript and jQuery it’s pretty easy to see what those do. The first one is really the important one. It takes in the ID of a page, uses that to construct a query to send to queryToJson, gets the returned content, parses it, and loads it into a div. You might be asking what the [link] [text] and [vflink] are about. We’ll cover those a bit later, but just know they are for letting users easily create javascript ajax links on your site.

The second function adds some more functionality. It allows users to create links that will load other visualforce page content. Sometimes you have a page that is too big or complex to be just a webpage object or you need some visualforce features. In that case, you can create a visualforce page for your user, then they can create links to load that content easily.

Now we have a site that is ready to deliver content. But what content? We don’t have anything to serve right now. That is what we are going to set up next. We’ll create a visualforce page that will be the main template that is responsible for data fetching and display. Just a heads up, this process it more suited for simpler sites, with one main skin, and the only content that changes is per page, like the overall template doesn’t change, just the content. Anyway, let’s create our visualforce page. This page won’t have a controller, so you could develop in prod, if you are feeling particularly reckless XD So go ahead and create a new visualforce page. Let’s just keep it simple for now. Something like

<apex:page standardStylesheets="false"  expires="0" cache="false" showHeader="false" sidebar="false">
<html>
    <head>
        <title>Our test page!</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
        
        <!--- PUT THAT JAVASCRIPT I GAVE YOU BEFORE IN HERE! ----->
        <!----If you put it in a component, to access it, add the controller attribute to the apex
              page, and specify your component. Then include the following snipped
              
               <apex:outputText escape="false">
                    <c:yourComponentName />
                </apex:outputText>
                
              If you are using a static resource, do something like 
              <script src="{!URLFOR($Resource.yourResource, 'javascriptStuff.js')}"></script> 
              
              otherwise, you can just copy and paste that code in here between <script> and </script>
        ----->      

        
    </head>
    
    <body>
        <div id="links" style="width:100%; height:100px; overflow:scroll border-bottom-style:solid;">
            <a href="javascript:loadPageSF('webpage page id');">Load this webpage into main textarea</a><br />
            <a href="javascript:loadPageVF('visualforce page id);">Load this visualforce page into the main textarea</a>
        </div>
        
        <div id="main-textarea" style="width:100%; height:500px; overflow:scroll border-bottom-style:solid;">
        
        </div>
    </body>

</html>
</apex:page>

And there you have it. Include that javascript, and change the arguments passed to those function calls in the links div and you should be able to load data. You can create a few webpage objects now and try loading their Ids.

Now one last thing for this article is, what are that extra stuff in the parser about, how do my users create those ajax links? Well of course, since this site is all ajax powered, we only really want ajax links (even though we arn’t really using xml, we are using json, so I guess it would be AJAJ?). But writting javascript links is kind of cumbersome and users don’t want to do that. So we give them the ability to write links like this.

[link]somePageId[text]This is an ajax link to a webpage[/text][/link]

or

[vflink]someVisualForcePageId[text]This is an ajax link to a visualforce page[/text][/vflink]

any user can write that! Then when those special tags are encountered by our parser, they can turn them into real usable links. You could of course expand my little scripting language and give you users other custom tags that do complicated actions. Just keep in mind, the more parsing, the slower the response times.

So that’s it for now. Next time we’ll cover how to make a nice editor for your users that won’t make them want to rip their hair out. Heads up, you’ll need to spend about 10 bucks for a component to it. Hopefully that doesn’t break your bank.

Oh, also, since I anticipate a few common questions, I’ll try and hit em before that happens.

Q: How can I make my site load a given page when the page loads?
A: Something like this should work. Put it in your javascript section.

jQuery(document).ready(function() 
{
     loadPageSF(pageID);  
}

Q: Since this is all ajax, how can I make people be able to do bookmarks, or get to a specific page without using links?
A: This is kind of tricky. I know some web developers use crazy hash-marks and exclamation points and weird URL hacks to make this work. I don’t totally understand how that all works, but here my solution. You can just pass in a ?page=whatever in the url and the site will load it. Easy bookmarking.

function getUrlVars()
{
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++)
    {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

jQuery(document).ready(function() 
{
    var pageToLoad = getUrlVars()['page'];
    if(typeof pageToLoad == 'undefined')
    {
        //if no page is passed in
        loadPageSF('home page');
    }
    else
    {
        //if a page is passed in
        loadPageSF(pageToLoad);
    }  
}

Q: How can I make the links be able to use page name instead of pageID.
A: Just change the query in the javascript to have the extra where condition. Like

var query = "page_content_new__c from webpage__c where id = '"+pageID+"' or title__c = '"+pageID+"'"

Q: How can I stop queryToJson from responding to any random request that might not be authorized?
A: Hm, tough call. I would send some kind of encrypted key in the URL, and have the queryToJson component decrypt it and check to make sure it is correct. Keep in mind queryToJson can only select data, and you could lock it down to only select from your website__C object or something. When I come up with a better solution I’ll include it in a new release. If anyone has a good solution for this, let me know.

That’s all for now. Hope if nothing else this might get your mental gears turning and thinking how else you can leverage this platform and maybe even have some fun doing it. Till next time.

6 responses

  1. Igor

    Hi Kenji,
    First of all, many thanks for your article. I’m new in the force.com platform and I would like to make you a couple of questions:
    - when I try to save the component I have created with your javascript code I get this error “Error: The reference to entity “callback” must end with the ‘;’ delimiter.”. Do you know why??
    - the second question refers to the Sites domain. I don’t know what domain I would like to set yet, so If I set a domain I think that in the future I couldn’t change it, so how could I try your solution?? (I am developing in Force.com under a developer license) Many thanks, and sorry for my english.
    Igor

    May 19, 2011 at 8:50 pm

    • Hey Igor,

      Don’t worry, your English is just fine ;)

      As for the first error, that is a bit of a head scratcher. It sounds like maybe a quote or something may be missing somewhere. Is this the component throwing that error? You can feel free to email me your code and I can look it over.

      If you don’t know ahead of time what your domain name is going to be, you may be able to store it is a variable in a custom setting, then access that setting in your component. I don’t know for sure if that would work, but it’s at least worth a shot. This whole system really does kind of require having a site setup though (at least in my mind) so I’m not sure how far you are going to get before you hit a wall you can’t get past. I guess if you where trying to make this as an intranet site, just available internally you could totally replace the URL request with a direct invocation of the Apex class that gets your data. That would bypass the whole need for a site. Does that make sense? I can try and rephrase if I am being confusing, which I feel I might be XD

      May 19, 2011 at 8:59 pm

      • For any of those that have the same problem, the solution is to make sure that the code in the component is wrapped in script tags.

        May 19, 2011 at 10:05 pm

  2. Sarah

    I’m trying to follow your steps but I can’t seem to download your queryToJson zip… It’s possible the server’s just slow and I’m being impatient :)

    Thanks!
    Sarah

    November 22, 2011 at 9:00 pm

  3. Yeah, my file host went down and I haven’t rehosted. Honestly queryToJson is badly out dated. With the new json serialize and deserialize it’s not really needed anymore. Also if you are going to be using Salesforce as a CMS you should really look into force.com sites before implementing your own. It’s much more powerful than anything you or I could build alone.

    November 22, 2011 at 9:06 pm

  4. Sarah

    Gotcha, thanks for letting me know. Guess I’ll try something else!

    November 22, 2011 at 9:50 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 591 other followers