Today’s post is a customization of the timeline available at codyhouse.co using knockout. This implementation focuses on the ability to extend the timeline adding template binding capabilities using knockout.js.

The final output will look exactly like the original one but there is a considerable amount of code change. If you haven’t still checked out timeline at codyhouse.co, now may be a good time.

Get the code here

You can download the entire customization from GitHub.

Lets begin !

We are going to supply a JSON array to this timeline and see it render based on the data that is present in this array. This is the first step towards achieving the extensibility we are aiming at. Templates enable us to style each type of timeline item independently. This is how the JSON array looks like. An explanation of the json follows.

[
  {
    "Type": "Picture",
    "Head": "Lorem ipsum dolor sit amet.",
    "MediaUrl": "http://www.lntrealty.com/images/projects/seawoods/overview.jpg",
    "Content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Perferendis alias quos nemo repudiandae vitae tenetur sapiente provident blanditiis. Quis, laborum, harum quos labore quasi maxime optio earum voluptates incidunt laudantium!",
    "Date": "8th Jan"
  },
  {
    "Type": "Location",
    "Head": "Ad, debitis, molestiae, dolorum molestias.",
    "Content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ad, at maxime necessitatibus rem voluptate nisi vel numquam temporibus expedita aspernatur. Accusamus, illo maxime reiciendis modi sed cumque officiis delectus porro.",
    "MoreLink": {
        "Text": "Read More..",
        "Href": "http://www.google.com"
    },
    "Date": "21st Jan"
  },
  {
    "Type": "Movie",
    "Head": "Incidunt, voluptatum quis non!",
    "MediaUrl": "https://www.youtube.com/embed/MejbOFk7H6c",
    "Content": "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Minima eligendi consequatur sint corporis. Ea, itaque, est, nihil voluptates id magnam molestiae omnis nostrum quisquam blanditiis facilis dolorum inventore dolorem impedit.",
    "MoreLink": {
        "Text": "Read More..",
        "Href": "http://www.google.com"
    },
    "Date": "11th Feb"
  }
]

Type : This element decides which template to render. We have 3 predefined templates named “Picture”, “Location”, “Movie”. You can add more templates to the existing ones. To do this, just add one more template to within the index.html and and define a new Type inside your JSON array. That easy!
Head : The Title to show for an item of the timeline
MediaUrl : A link to either an image or video.
Content : The text to show for an item of the timeline.
MoreLink (optional) : A read more link
Date : Date for the timeline

Building the Data And View Models

Lets start with the ViewModel function. In the ViewModel we fetch the data using $.getJSON. We then use the $.map function to push each object of the JSON array to the TemplateItems array (ko.observableArray). Objects in the array are of the type DataModel. The DataModel is fairly self explanatory and corresponds to the JSON array that we supplied earlier.

Adding the bounce animation

To achieve this we use the afterRender callback provided by KO. Each time a template is rendered the function self.addAnimation is called. This function contains our logic for making the elements bounce.


function DataModel(data){
    var self = this;
    self.Type = data.Type;
    self.Head = data.Head;
    self.MediaUrl = data.MediaUrl?data.MediaUrl : false;
    self.Content = data.Content;
    self.MoreLink = data.MoreLink? {
                        Text : data.MoreLink.Text, 
                        Href : data.MoreLink.Href
                    }: false;
    self.Date = data.Date;
}

function ViewModel(){
    var self=this;
    
    self.TemplateItems = ko.observableArray([]);
    
    self.addAnimation = function(elements){
        $( elements ).each(function(){
            if($(this).offset().top > $(window).scrollTop()+$(window).height()*0.75) {
                $(this).find('.cd-timeline-img, .cd-timeline-content').addClass('is-hidden');
            }        
        });
        
        //on scolling, show/animate timeline blocks when enter the viewport
        $(window).on('scroll', function(){
            $(elements).each(function(){
                if( $(this).offset().top <= $(window).scrollTop()+$(window).height()*0.75 && $(this).find('.cd-timeline-img').hasClass('is-hidden') ) {
                $(this).find('.cd-timeline-img, .cd-timeline-content').removeClass('is-hidden').addClass('bounce-in');    
                }    
            });    
        });
    };
    
    //contructor.
    $.getJSON("data.json")
            .done(function( result ){
                    $.map(result,function(item){
                        self.TemplateItems.push(new DataModel(item))
                    });                
            })
            .fail(function( jqxhr, textStatus, error ) {
                    var err = textStatus + ", " + error;
                    console.log( "Request Failed: " + err );
            });    
}

jQuery(document).ready(function($){
    var obj = new ViewModel();
    ko.applyBindings(obj);
});

Finally, Wrap it up in the HTML !

Once we have created our JS we now  wrap things up by hooking everything together in the HTML.
The <section> tag now references the TempalteItems array we created earlier. We loop through each item in that array to load a specific type of template governed by the value contained in the Type variable within the JSON. This value should correspond to one of the templates defined in the markup.

<section id="cd-timeline" class="cd-container" data-bind="foreach: TemplateItems">
  <!-- ko template: {name: Type, afterRender: $parent.addAnimation}--><!-- /ko -->
</section> <!-- cd-timeline -->

<!-- Template Type Picture -->
<script type="text/html" id="Picture">
  <div class="cd-timeline-block">
	<div class="cd-timeline-img cd-picture">
	  <img src="img/cd-icon-picture.svg" alt="Picture">
	</div> <!-- cd-timeline-img -->
	<div class="cd-timeline-content">
	  <h2 data-bind="text:Head"></h2>
	  <!-- ko if: MediaUrl -->
	  <p>
		<img data-bind="attr:{src:MediaUrl}" class="img-responsive center-block"/>
	  </p>
	  <!-- /ko-->
	  <p data-bind="text: Content"></p>
	  <!-- ko if: MoreLink -->
	  <a data-bind="attr:{href:MoreLink.Href},text:MoreLink.Text" class="cd-read-more"></a>
	  <!-- /ko-->
	  <span data-bind="text: Date" class="cd-date"></span>
	</div> <!-- cd-timeline-content -->
  </div> <!-- cd-timeline-block -->
</script>

<!-- Template Type Movie -->
<script type="text/html" id="Movie">
  <div class="cd-timeline-block">
    <div class="cd-timeline-img cd-movie">
      <img src="img/cd-icon-movie.svg" alt="Movie">
    </div> <!-- cd-timeline-img -->
    <div class="cd-timeline-content">
      <h2 data-bind="text:Head"></h2>
      <div>
        <!-- ko if: MediaUrl -->
        <p class="embed-responsive embed-responsive-16by9">
          <iframe class="embed-responsive-item" data-bind="attr:{src:MediaUrl}" allowfullscreen></iframe>
        </p>
	    <!-- /ko-->
        <p data-bind="text: Content"></p>
      </div>
      <!-- ko if: MoreLink -->
	  <a data-bind="attr:{href:MoreLink.Href},text:MoreLink.Text" class="cd-read-more"></a>
	  <!-- /ko-->
	  <span data-bind="text: Date" class="cd-date"></span>
    </div> <!-- cd-timeline-content -->
  </div> <!-- cd-timeline-block -->
</script>

<!-- Template Type Location -->
<script type="text/html" id="Location">
  <div class="cd-timeline-block">
    <div class="cd-timeline-img cd-location">
      <img src="img/cd-icon-location.svg" alt="Location">
    </div> <!-- cd-timeline-img -->
    <div class="cd-timeline-content">
      <h2 data-bind="text:Head"></h2>
      <div>
        <p data-bind="text: Content"></p>
      </div>
       <!-- ko if: MoreLink -->
	  <a data-bind="attr:{href:MoreLink.Href},text:MoreLink.Text" class="cd-read-more"></a>
	   <!-- /ko-->
	  <span data-bind="text: Date" class="cd-date"></span>
    </div> <!-- cd-timeline-content -->
  </div> <!-- cd-timeline-block -->
</script>

That’s it for now. If you have any comments or suggestions do let me know in the comments section below. Till then, keep it classy ! Cheers 🙂