Let’s finish 2016 continuing with the series of custom components, in this case we will create a custom component to integrate Oracle Content Marketing (a.k.a Compendium) with Oracle Sites Cloud Service.

Oracle Content Marketing makes it easy for everyone in your organization (and even your customers) to create and distribute compelling content across multiple channels to the people you want to reach. You can plan, produce, and deliver engaging content across multiple personas and channels throughout the customer lifecycle. In this example, we are going to integrate in Sites Cloud Service a Compendium content generated by a marketer using all the Content Marketing Cloud capabilities.

We will use the same Bootstrap theme that we have created in a previous post (Modern Business).

Same as we did for the custom slider and custom Eloqua components, first step is go to Components section and create a new Local Component:


Once the component is created and synchronized with our computer, we can see the default component structure in our favorite editor:


Let’s start by the component definition, so let’s open the appinfo.json file and modify it to reflect our specific needs (we don’t need nested components, neither default custom settings value, so let’s keep it pretty much clean):

"id": "compendium-content-component",
"settingsData": {
"settingsHeight": 50,
"settingsWidth": 300,
"settingsRenderOption": "inline"

Next step is customize the settings panel. Same as we did with the component definition, we don’t need most part of the generated code, as we only will have a dropdown that will list the available Compendium Contents and we will only store the Content Id in our custom settings. The only interesting thing in the below code, is the ajax call to retrieve the Compendium contents and populate the dropdown with the different values (using title and id keys)

<!DOCTYPE html>
<html lang="en">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Compendium Content settings</title>

<!-- include sample apps styling -->
				<link href="/_sitescloud/renderer/app/sdk/css/app-styles.css" rel="stylesheet">

<!-- include supporting files -->
<script type="text/javascript" src="/_sitescloud/renderer/app/apps/js/knockout.min.js"></script>
<script type="text/javascript" src="/_sitescloud/renderer/app/apps/js/jquery.min.js"></script>

<!-- include the Sites SDK -->
<script type="text/javascript" src="/_sitescloud/renderer/app/sdk/js/sites.min.js"></script>
<body data-bind="visible: true" style="display:none; margin:0px; padding:0px;background:transparent;background-image:none;">
<!-- ko if: initialized() -->

<div class="scs-component-settings">

<!-- Content -->
<label id="contentIdLabel" for="contentId" class="settings-heading" data-bind="text: 'Content'"></label>
<select class="settings-select" id="contentId" data-bind="options: contents, &nbsp; optionsText: 'name', &nbsp; optionsValue: 'id', &nbsp; value: contentId, &nbsp; optionsCaption: 'Choose...'">


<div data-bind="setSettingsHeight: true"></div>

<!-- /ko -->
<!-- ko ifnot: initialized() -->

<div data-bind="text: 'waiting for initialization to complete'"></div>

<!-- /ko -->
<script type="text/javascript">

// set the iFrame height when we've fully rendered
ko.bindingHandlers.scsCompComponentImpl = {
  init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
    var body = document.body,
    html = document.documentElement;

// define the viewModel object
var SettingsViewModel = function () {
  var self = this;
  self.contentId = ko.observable();
  self.contents = ko.observableArray();

  var Object = function(name, id) {
    this.name = name;
    this.id = id;

// create rest of viewModel
self.initialized = ko.observable(false);
self.saveData = false;

// Get custom settings
SitesSDK.getProperty('customSettingsData', function (data) {
  //update observable
  self.saveData = true;

// save whenever any updates occur
self.save = ko.computed(function () {
  var saveconfig = {
    'contentId': self.contentId()

  // save data in page
  if (self.saveData) {
    SitesSDK.setProperty('customSettingsData', saveconfig);
}, self);

//AJAX call to my custom nodeJS proxy to avoid CORS issues
  url: "https://localhost:8443/contents",
  type: "GET",
  dataType: 'json',
  beforeSend: function (xhr) {
    xhr.setRequestHeader('Authorization', '<strong>YOUR_COMPENDIUM_KEY</strong>');
  success: function(data,status) {
    var content = data.content;
    $.each(content, function(key, element) {
      self.contents.push(new Object(element.title,element.id));
  error: function (text,status) { console.log('error ' + JSON.stringify(text)) }

// apply the bindings
ko.applyBindings(new SettingsViewModel());

In the previous post, integrating Eloqua with SCS, I’ve discovered the CORS issue and explained three ways to avoid the issue. In that example, I’ve used a Chrome plugin to modify the headers and avoid CORS problem. In this example, I’ve build a really simple nodeJS app that is in charge of act as a proxy to all my requests to Compendium while adding the specific headers to allow Cross Origin Resource Sharing. Now we can do a quick test and include our custom component in a SCS page to see if settings panel is working as expected. Using my proxy, the REST call works as expected and I’m retrieving the contents from Content Marketing Cloud:

We can test how the content selected is saved and if we reopen the settings, the dropdown selection is our content.

The next step, is the render part, which in this example, same as in the Eloqua one, is really easy as we just need to get the content id stored in the custom settings and make a REST call to Compendium API (using our nodeJS proxy) and get title, body and featured_image to be displayed in the component. Your final render.js file should looks like this (depending on your own logic, I’ve added a condition to display differently the content when the content_type match a specific type):

/* globals define */
define(['knockout', 'jquery'], function (ko, $) {
'use strict';
// ----------------------------------------------
// Define a Knockout Template for your component
// ----------------------------------------------
var sampleComponentTemplate = '<!-- ko if: initialized -->' +
'<!-- ko if: contentId -->' +
<div class="panel panel-default">' +
<div class="panel-heading">' +
<h4 data-bind="text: title"></h4>
' +
' +
<div class="panel-body">' +
'<!-- ko if: infographic()===true -->' +
'<img class="col-md-6 img-responsive img-hover" data-bind="attr: { src: featured_image }" width=""/>' +
<div class="col-md-6" data-bind="html: body"></div>
' +
'<!-- /ko -->' +
'<!-- ko if: infographic()===false -->' +
<div data-bind="html: body"></div>
' +
'<!-- /ko -->' +
' +
' +
'<!-- /ko -->' +
'<!-- /ko -->';

// ----------------------------------------------
// Define a Knockout ViewModel for your template
// ----------------------------------------------
var SampleComponentViewModel = function (args) {
var self = this,
SitesSDK = args.SitesSDK;

// store the args
self.mode = args.viewMode;
self.id = args.id;

// create the observables
self.contentId = ko.observable();
self.title = ko.observable();
self.featured_image = ko.observable();
self.body = ko.observable();
self.infographic = ko.observable(false);

// handle initialization
self.customSettingsDataInitialized = ko.observable(false);
self.initialized = ko.computed(function () {
return self.customSettingsDataInitialized();
}, self);

// Handle property changes

self.updateCustomSettingsData = $.proxy(function (customData) {
self.contentId(customData && customData.contentId);
if(customData && customData.contentId){
headers : {
'Authorization' : '<strong>YOUR_OWN_KEY</strong>'
$.getJSON("https://localhost:8443/content?id="+self.contentId(),function(data) {
}, self);
self.updateSettings = function (settings) {
if (settings.property === 'customSettingsData') {

// listen for the EXECUTE ACTION request to handle custom actions
SitesSDK.subscribe(SitesSDK.MESSAGE_TYPES.EXECUTE_ACTION, $.proxy(self.executeActionsListener, self));
// listen for settings update
SitesSDK.subscribe(SitesSDK.MESSAGE_TYPES.SETTINGS_UPDATED, $.proxy(self.updateSettings, self));

// Initialize customSettingsData values
SitesSDK.getProperty('customSettingsData', self.updateCustomSettingsData);

// ----------------------------------------------
// Create a knockout based component implemention
// ----------------------------------------------
var SampleComponentImpl = function (args) {
// Initialze the custom component
// initialize all the values within the component from the given argument values
SampleComponentImpl.prototype.init = function (args) {
// create the viewModel from the initial values
SampleComponentImpl.prototype.createViewModel = function (args) {
// create the viewModel
this.viewModel = new SampleComponentViewModel(args);
// create the template based on the initial values
SampleComponentImpl.prototype.createTemplate = function (args) {
// create a unique ID for the div to add, this will be passed to the callback
this.contentId = args.id + '_content_' + args.mode;
// create a hidden custom component template that can be added to the DOM
this.template = '
<div id="' + this.contentId + '">' +
sampleComponentTemplate +
// SDK Callbacks
// setup the callbacks expected by the SDK API
SampleComponentImpl.prototype.setupCallbacks = function () {
// callback - render: add the component into the page
this.render = $.proxy(function (container) {
var $container = $(container);
// add the custom component template to the DOM
// apply the bindings
ko.applyBindings(this.viewModel, $('#' + this.contentId)[0]);
}, this);
// callback - update: handle property change event
this.update = $.proxy(function (args) {
var self = this;
// deal with each property changed
$.each(args.properties, function (index, property) {
if (property) {
if (property.name === 'customSettingsData') {
}, this);
// callback - dispose: cleanup after component when it is removed from the page
this.dispose = $.proxy(function () {
// nothing required for this sample since knockout disposal will automatically clean up the node
}, this);
// ----------------------------------------------
// Create the factory object for your component
// ----------------------------------------------
var sampleComponentFactory = {
createComponent: function (args, callback) {
// return a new instance of the component
return callback(new SampleComponentImpl(args));
return sampleComponentFactory;

Let’s test if the content is rendered when we select it from the settings panel:


The most useful learning of this example is how a Content Marketing Cloud marketer can define specific contents to be included in webpages and how a Sites Cloud Service contributor, can easily access to the Content Marketing contents from a custom component and including the content in a SCS page without a single line of code, just selecting the desired Content from the settings panel.

In a next post, I will enhance this component to display two different combos related between them. In the first one, we will display the list of Personas that are created in Content Marketing Cloud and when we select a Persona, we will see in the second dropdown the list of contents that have been tagged to this specific Persona. By doing this, we can improve the Sites Cloud Service Contributor experience allowing to select content that has been created for a Persona (that represents a segment of people). But this is for a next post, so in the meantime, enjoy Sites Cloud Service and continue looking at your own use cases.

Happy New Year!


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 )

Google photo

You are commenting using your Google 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 )

Connecting to %s