In this post I will show an example of a custom carousel component built with Sites Cloud Service. When working with specific designs, most part of the carousels are specifically created for the design, including the required JS files as well as the Stylesheet, so in this post I will take a bootstrap template, create a Sites Cloud Service Theme based on it and create the custom component to replace the theme carousel with a slot in which a business user can drag&drop the component and configure it as they wish.

For the first step (create a SCS Theme from a Bootstrap template), I don’t want to reinvent the wheel, so you can go to Igor’s Musings blog and have a look at the series of posts about how to create a Theme in Sites Cloud Service (part 1).

Once we have the theme already created, let’s start with the process of convert the static slider on the top of the page in a dynamic component to be used by a contributor.

Edit your index.html layout that you have already synchronized with Documents Desktop Sync Tool and search for the header tag that has the slider:

slidercode

Replace the slider code (copy the original code as we are going to use it later to build our component) by the following:

slotcode

With the above code, you are creating a slot that can be used by a contributor to drag&drop specific components to the page. Now, Sites Builder should looks like this:

sitesbuilder

By now, we have our layout ready to use, so let’s move to the component creation. Navigate to Components section, create a new local component and sync the new component with your local computer:

createcomponent

Once created, let’s have a look at the default component structure:

componentsstructure

In the assets folder, create a new html file, call it as you want (in my case template.html) and paste the original slider code. Also you need to include the script to activate the slider

<!-- Indicators -->
<ol class="carousel-indicators">
	<li data-target="#myCarousel" data-slide-to="0" class="active"></li>
	<li data-target="#myCarousel" data-slide-to="1"></li>
	<li data-target="#myCarousel" data-slide-to="2"></li>
</ol>
<!-- Wrapper for slides -->
<div class="carousel-inner">
<div class="item active">
<div class="fill" style="background-image:url('//placehold.it/1900x1080&text=Slide One');"></div>
<div class="carousel-caption">
<h2>Caption 1</h2>
</div>
</div>
<div class="item">
<div class="fill" style="background-image:url('//placehold.it/1900x1080&text=Slide Two');"></div>
<div class="carousel-caption">
<h2>Caption 2</h2>
</div>
</div>
<div class="item">
<div class="fill" style="background-image:url('//placehold.it/1900x1080&text=Slide Three');"></div>
<div class="carousel-caption">
<h2>Caption 3</h2>
</div>
</div>
</div>
<!-- Controls -->
<a class="left carousel-control" href="#myCarousel" data-slide="prev">
<span class="icon-prev"></span>
</a>
<a class="right carousel-control" href="#myCarousel" data-slide="next">
<span class="icon-next"></span>
</a>
<!-- Script to Activate the Carousel -->
<script>
  if ( $( ".carousel" ).length ) {
    $('.carousel').carousel({
      interval: 5000 //changes the speed
    })
  }
</script>

The next step is to use Mustache to load our (currently static) template, so let’s download the required JS library that can be obtained directly from this link and save it in the assets folder.

Edit render.js file and place the following code:

/* globals define */
define(['jquery', './mustache.min', 'text!./template.html'], function($, Mustache, template) {
'use strict';

// ----------------------------------------------
// Create a Mustache based component implementaion
// ----------------------------------------------
var SampleComponentImpl = function(args) {
this.SitesSDK = args.SitesSDK;

// Initialze the custom component
this.createTemplate(args);
this.setupCallbacks();
};
// 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.viewMode;
// create a hidden custom component template that can be added to the DOM
this.template = '
<div id="' + this.contentId + '">' +
template +
'</div>
';
};
SampleComponentImpl.prototype.updateSettings = function(settings) {
if (settings.property === 'customSettingsData') {
this.update(settings.value);
}
};
SampleComponentImpl.prototype.update = function(data) {
this.data = data;
this.container.html(Mustache.to_html(this.template, this.data));
};
//
// 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) {
this.container = $(container);
this.SitesSDK.getProperty('customSettingsData', $.proxy(this.update, this));
}, this);
//
// callback - SETTINGS_UPDATED: retrive new custom data and re-render the component
//
this.SitesSDK.subscribe(this.SitesSDK.MESSAGE_TYPES.SETTINGS_UPDATED, $.proxy(this.updateSettings, this));
//
// callback - dispose: cleanup after component when it is removed from the page
//
this.dispose = $.proxy(function() {
// nothing required
}, 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;
});

This code will render a mustache template called template.html located in the same path.

The latest thing that we need to do is to adapt the styles to fit into CSS3. As we are modifying dynamically the page DOM, specifically for the slider, we need to modify how the slider height is dynamically calculated. Replace following code in modern-business.css file:

/* Home Page Carousel */

header.carousel {
    height: 50%;
}

header.carousel .item,
header.carousel .item.active,
header.carousel .carousel-inner {
height: 100%;
}

header.carousel .fill {
    width: 100%;
    height: 100%;
    background-position: center;
    background-size: cover;
}

with this one:

/* Home Page Carousel */

header.carousel {
    height: 50vh;
}

header.carousel .item,
header.carousel .item.active,
header.carousel .carousel-inner {
    height: 50vh;
}

header.carousel .fill {
    width: 100%;
    height: 50vh;
    background-position: center;
    background-size: cover;
}

Let’s give a try using Sites Builder to check that we can drag&drop our component on the page and also that it renders our template.

mustachecomponent

The next step is make the component dynamically populated with images selected by a contributor, so as the first step, we are going to clean up the default settings created by the seeded component during the creation.

First of all, open appinfo.json a replace the code by the following one:

{
"id": "moderbusiness-carousel",
"settingsData": {
"settingsWidth": 300,
"settingsRenderOption": "panel"
},
"initialData": {
"componentId": "moderbusiness-carousel-id",
"componentLayout": "default",
"customSettingsData": {
},
"nestedComponents": [
]
}
}

Now we are going to clean up the settings screen and build the specific settings that we need for our component (select a list of images). Replace settings.html code with the following one:

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

<title>Modern Business carousel Module 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>
</head>
<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">


<div data-bind="html: imageName"></div>


  <button id="imageSelect" type="button" class="save-button" data-bind="click: showFilePicker">Select Images</button>
</div>



<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">
  // define the viewModel object
  var SettingsViewModel = function () {
  var self = this;
  // create rest of viewModel
  self.initialized = ko.observable(false);
  self.saveData = false;

  //
  // handle component assets
  //
  self.assets = ko.observableArray([]);
  self.imageIDs = ko.observable();

  // bring up a file picker to select the assets
  showFilePicker = function () {
    // select an image
    SitesSDK.filePicker({
      'multiSelect': true,
      'fileTypes': ['png','jpg']
    }, function (result) {
      self.assets = result;
      var assetids = '';
      ko.utils.arrayForEach(result, function(item) {
        // update the string of images
        assetids += item.id.toString() + ',';
      });
      assetids = assetids.slice(0, -1);
      self.imageIDs(assetids);
    });
  };

 // update the display name based on the assets
 self.imageName = ko.computed(function () {
   var imageName = '';
   var anotherImageIDs = self.imageIDs();
   for (var i = 0; i < self.assets.length; i++) {
     imageName += "<img src='" + self.assets[i].url + "' width='150' /><input style='width:280px;' id=\"imageAssets" + i + "\" value=\"" + self.assets[i].fileName + "\" readonly class=\"settings-text-box\" />";
   }
   return imageName;
 }, self);

 // Get component assets
 SitesSDK.getProperty('componentAssets', function (assets) {
   self.assets = assets;
   // Get custom settings
   SitesSDK.getProperty('customSettingsData', function (data) {
     //update observable
     self.imageIDs(data.imageIDs);
     self.initialized(true);
     self.saveData = true;
   });
 });

 // save whenever any updates occur
 self.save = ko.computed(function () {
   var saveconfig = {
     'imageIDs': self.imageIDs()
   };
   // save data in page
   if (self.saveData) {
     SitesSDK.setProperty('componentAssets', self.assets);
     SitesSDK.setProperty('customSettingsData', saveconfig);
   }
 }, self);
 };
 // apply the bindings
 ko.applyBindings(new SettingsViewModel());
 </script>
</body>
</html>

Now, we can go to the settings screen and check how we can do multiple selection of images from Documents Cloud Service using the File Picker JS API

The last step is to modify render.js and our HTML template to render dynamically the objects that are sent from render.js using mustache specific tags, so we just require to do a modification and add the images urls (that are calculated dynamically in run-time) as part of the object that is passed to the mustache template

Mustache template will looks like the following:


{{#images.0}}
<!-- Indicators -->
<ol class="carousel-indicators">
{{#images}}
{{#first}}
	<li data-target="#myCarousel" data-slide-to="{{index}}" class="active"></li>
{{/first}}
{{^first}}
	<li data-target="#myCarousel" data-slide-to="{{index}}"></li>
{{/first}}
{{/images}}</ol>
<!-- Wrapper for slides -->
<div class="carousel-inner">
{{#images}}
{{#first}}
<div class="item active">
{{/first}}
{{^first}}
<div class="item">
{{/first}}
<div class="fill" style="background-image:url({{url}});"></div>
</div>
{{/images}}</div>
<!-- Controls -->
<a class="left carousel-control" href="#myCarousel" data-slide="prev">
<span class="icon-prev"></span>
</a>
<a class="right carousel-control" href="#myCarousel" data-slide="next">
<span class="icon-next"></span>
</a>
<!-- Script to Activate the Carousel -->
<script>
  if ( $( ".carousel" ).length ) {
    $('.carousel').carousel({
      interval: 5000 //changes the speed
    })
  }
</script>
{{/images.0}}
{{^images.0}}
<div class="scs-gallery-edit scs-watermark">
<div class="scs-watermark-image">Modern Business Carousel</div>
</div>
{{/images.0}}

And render.js file will look like the following code:


/* globals define */
define(['jquery', './mustache.min', 'text!./template.html'], function($, Mustache, template) {
'use strict';

// ----------------------------------------------
// Create a Mustache based component implementaion
// ----------------------------------------------
var SampleComponentImpl = function(args) {
this.SitesSDK = args.SitesSDK;

// Initialze the custom component
this.createTemplate(args);
this.setupCallbacks();
};
// 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.viewMode;
// create a hidden custom component template that can be added to the DOM
this.template = '
<div id="' + this.contentId + '">' +
template +
'</div>
';
};
SampleComponentImpl.prototype.updateSettings = function(settings) {
if (settings.property === 'customSettingsData') {
this.update(settings.value);
}
};
SampleComponentImpl.prototype.update = function(data) {
var imageUrls = [];
this.SitesSDK.getProperty('componentAssets', function (assets) {
console.log("assets: " + JSON.stringify(assets) + " length:" + assets.length);
var imageUrl = '';
if(assets.length>0){
for(var i=0;i<assets.length;++i){
imageUrl = assets[0].url;
imageUrls.push({'index': i, 'url': imageUrl, 'first':i==0});

}
console.log("imageURLs: " + JSON.stringify(imageUrls));
data["images"] = imageUrls;
}
});
this.data = data;
this.container.html(Mustache.to_html(this.template, this.data));
};
//
// 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) {
this.container = $(container);
this.SitesSDK.getProperty('customSettingsData', $.proxy(this.update, this));
}, this);
//
// callback - SETTINGS_UPDATED: retrive new custom data and re-render the component
//
this.SitesSDK.subscribe(this.SitesSDK.MESSAGE_TYPES.SETTINGS_UPDATED, $.proxy(this.updateSettings, this));
//
// callback - dispose: cleanup after component when it is removed from the page
//
this.dispose = $.proxy(function() {
// nothing required
}, 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;
});

Now, when we include the component in our page, we can dynamically add images to our carousel and our component is able to render them.

componentfinishedcomponentrendered

Once we save and preview in a new window, we have our Modern Business Bootstrap template working with a dynamic carousel.

Advertisements

2 thoughts on “Building a custom carousel with Sites Cloud Service

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