Skip to content

Bootstrap Visual Timeline Component

Components are king!  Working on projects & applications, I often find myself digging though old code to find pieces to copy and reuse – especially when it comes to UI/UX.  About a year ago I started to build a library of VisualForce components that use javascript (jQuery) and Bootstrap after I discovered Visualstrap (VS) by Avinava Maiti.  He was able to build out a package with most of the Bootstrap 2 components in a Visualforce freindly fashion which makes building page with bootstrap streamlined and easy on the eyes from a coding perspective, here’s an example of implementing a panel with header (after implementing the 2 required VS components):

Bootstrap:

<div class="panel panel-default">
    <div class="panel-heading">
        <h3 class="panel-title">Panel title</h3>
    </div>
    <div class="panel-body">
        Panel content
    </div>
</div>

Visualstrap:

<vs:panel title="Panel Title" type="default">
    Panel Content
</vs:panel>

Both of these produce the same output, but with less coding under the Visualstrap component.
Screen Shot 2015-05-04 at 8.51.45 AM
Another advantage with using a VF component is the ability to target them with rendering actions from other form components.

The component I’m building is a visual timeline that can be used on any object with a date, and two string fields (title and description).  I found the CSS for this on bootdey.com, written by Deyson Bejarano.  I used one class as the controller, one component and one static resource for the CSS.  I have this demo setup as a page that’s uses Accounts as a standard controller, I’m displaying closed tasks for that account.  The timeline will look like this:

Screen Shot 2015-05-05 at 7.01.13 AM

Class:


global with sharing class bootTimeline {

global string queryString{get;set;}
global string dateString{get;set;}
global string titleString{get;set;}
global string descString{get;set;}

global map<string,list<timeline>> getLineMap(){
    map<string,list<timeline>> lineMap = new map<string,list<timeline>>();

    for(sObject s:database.query(queryString)){

        date thisDate = (date)s.get(dateString);

        timeline line = new timeline();
        line.lineMonth = monthYear(thisDate);
        line.lineDate = thisDate;
        line.dayName = dayOfWeek(thisDate);
        line.lineId = (string)s.get('id');
        line.lineTitle = (string)s.get(titleString);
        line.lineDesc = (string)s.get(descString);

        if(line.lineDesc.length() > 100)line.lineDesc = line.lineDesc.substring(0,100)+'...';

        list<timeline> lines = new list<timeline>();

        if(lineMap.get(line.lineMonth) != null)lines.addAll(lineMap.get(line.lineMonth));

        lines.add(line);

        lineMap.put(line.lineMonth,lines);

    }

    for(string s:lineMap.keySet()){

        list<timeline> lines = lineMap.get(s);
        lines.sort();
        lineMap.put(s,lines);

    }

    return lineMap;

}

global string monthYear(date thisDate){

    return monthMap.get(thisDate.month())+' '+thisDate.year();

}

global string dayOfWeek(date thisDate){

    date startOfWeek = thisDate.toStartOfWeek();

    integer daysApart = startOfWeek.daysBetween(thisDate);

    return dayShortMap.get(daysApart);

}

global final map<integer,string> monthMap = new map<integer,string>{
    1 => 'January',
    2 => 'February',
    3 => 'March',
    4 => 'April',
    5 => 'May',
    6 => 'June',
    7 => 'July',
    8 => 'August',
    9 => 'September',
    10 => 'October',
    11 => 'November',
    12 => 'December'
};

global final map<integer,string> dayShortMap = new map<integer,string>{
    0=>'Sun',
    1=>'Mon',
    2=>'Tue',
    3=>'Wed',
    4=>'Thu',
    5=>'Fri',
    6=>'Sat'
};

global class timeline implements Comparable{
    global string lineMonth{get;set;}
    global date lineDate{get;set;}
    global string dayName{get;set;}
    global string lineId{get;set;}
    global string lineTitle{get;set;}
    global string lineDesc{get;set;}

    global Integer compareTo(Object compareTo) {
        timeline compareToEmp = (timeline)compareTo;
        if (lineDate == compareToEmp.lineDate) return 0;
        if (lineDate > compareToEmp.lineDate) return -1;
        return 1;
    }
}

}

Component:

<apex:component controller="bootTimeline">

    <apex:attribute name="requireBS" type="boolean" default="true" description="Is bootstrap loaded already"></apex:attribute>
    <apex:attribute name="requireFA" type="boolean" default="true" description="Is font awesome loaded already"></apex:attribute>
    <apex:attribute name="query" type="string" required="true" assignTo="{!queryString}" description="Query for the timeline"></apex:attribute>
    <apex:attribute name="dateField" type="string" required="true" assignTo="{!dateString}" description="Date or DateTime field to base the timeline on"></apex:attribute>
    <apex:attribute name="titleField" type="string" required="true" assignTo="{!titleString}" description="Title field"></apex:attribute>
    <apex:attribute name="descField" type="string" required="true" assignTo="{!descString}" description="Description field"></apex:attribute>

    <apex:outputPanel rendered="{!requireBS}">
        <link href="https://netdna.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"></link>
    </apex:outputPanel>
    <apex:outputPanel rendered="{!requireFA}">
        <link rel="stylesheet" type="text/css" href="https://netdna.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css"/>
    </apex:outputPanel>

    <link href="{!URLFOR($Resource.bootTimeline_css)}" rel="stylesheet"></link>

    <div class="container">
        <section id="news" class="white-bg padding-top-bottom">
            <div class="container">
                <div class="timeline">
                    <apex:repeat value="{!lineMap}" var="monthGroup">
                        <div class="date-title">
                            <span>{!monthGroup}</span>
                        </div>
                        <div class="row">
                            <apex:variable value="{!0}" var="counter"></apex:variable>
                            <apex:repeat value="{!lineMap[monthGroup]}" var="line">
                                <div class="col-sm-6 news-item {!IF(MOD(counter,2) == 0,'','right')}">
                                    <div class="news-content">
                                        <div class="date">
                                            <p>{!DAY(line.lineDate)}</p>
                                            <small>{!line.dayName}</small>
                                        </div>
                                        <h2 class="news-title">{!line.lineTitle}</h2>
                                        <p>{!line.lineDesc}</p>
                                        <a class="read-more" href="/{!line.lineId}" target="_blank">
                                            Read More
                                        </a>
                                    </div>
                                </div>
                                <apex:variable value="{!counter+1}" var="counter"></apex:variable>
                            </apex:repeat>
                        </div>
                    </apex:repeat>
                </div>
            </div>
        </section>
    </div> 

</apex:component>

The component has 6 attributes, 4 are required that control the data being displayed and the other 2 handle loading bootstrap css and font-awesome.

  • query: enter the query for the timeline to be run, you’ll need to include the date, title and description fields.  It’s also best to order this by the date field descending.
  • dateField: this is the api name of the date field
  • titleField: this is the api name of the title field, it can be any string field but should be one that is normally less than 80 characters like a standard name field
  • descField: this is the api name of the description field, if can be any string field.  The first 100 characters will display on screen.

Once the above are built, the timeline can be implemented on a VF page by the following line:


<c:bootTimeline
query="SELECT id, subject, description, activityDate FROM task WHERE accountId = '{!Account.Id}' AND isClosed = true ORDER BY activityDate desc"
dateField="activityDate"
titleField="subject"
descField="description">
</c:bootTimeline>

I’m not going to post the CSS as it’s to long, instead here is a link to it here. The final version can be previewed at https://bt-dashboard-developer-edition.na24.force.com/?id=0011a000003RIcV. Enjoy!

Published inApexBootstrapComponentsCSSVisualforce

21 Comments

  1. 鏂板搧!!鏃ョ珛瑁藉鎺涖亼褰紭棣姏銉戙儍銈便兗銈搞偍銈偝銉炽€€鍊嬪垾閬嬭虎銉曘偐銉笺優銉儊銈裤偆銉?銈汇儍銉堝瀷寮?RPK-AP224GHW4 闆绘簮銈裤偆銉?涓夌浉锛掞紣锛愶级銈裤偆銉?銉儮銈炽兂锛氥儻銈ゃ儰銉偣銉儮銈炽兂 妤嫏鐢ㄣ偍銈偝銉炽伄鍙栦粯宸ヤ簨銈傘亰浠汇仜涓嬨仌銇勩€傚垾閫斿尽瑕嬬銈婅嚧銇椼伨銇?!

    I read this paragraph completely on the topic of the difference of most up-to-date and preceding technologies, it’s amazing article.

  2. 銆愰€佹枡鐒℃枡銆戙儛銉冦偘銉诲皬鐗┿兓銉栥儵銉炽儔闆戣波銆€銉儑銈c兗銈广儛銉冦偘銆€銉儑銈c兗銈广€€銉儓銉儭銉冦偦銉炽偢銉c兗銉愩儍銈般儊銈с兗銉炽偣銉堛儵銉冦儣銈枫儳銉儉銉笺偗銉┿儍銉併儛銉冦偘fasot womens retro leather crossbody chain wrist shoulder clutch bag

    Heya i’m for the first time here. I came across this board and I find It really useful & it helped me out a lot. I hope to give something back and aid others like you aided me.

  3. 銆愰€佹枡鐒℃枡銆戙偣銉濄兗銉勩兓瑷樺康鍝併€€銈炽儸銈偡銉с兂銆€銈儸銈淬兂銉撱兗銉愩兗銈ゃ兂銉併偆銉炽儊銈儍銉堛儑銈兗銉儉銈ゃ偊銈c兂oregon state beavers icial ncaa 8 inch x 8 inch die cut car decal by wincraft

    Today, I went to the beach front with my children. I found a sea shell and gave it to my 4 year old daughter and said “You can hear the ocean if you put this to your ear.” She placed the shell to her ear and screamed. There was a hermit crab inside a…

  4. 銉炪兗銈枫儯銉儻銉笺儷銉?銆岋紥鐐广偦銉冦儓銆嶃€€銈广兗銉戙兗銉戙儻銉笺儵銉冦偗?銉┿儛銉笺儛銉笺儥銉儉銉炽儥銉偦銉冦儓銆€锛戯紣锛曪綃锝?锛戯紣锛曪綃锝囥偦銉冦儓 銈枫儯銉曘儓锛掞綅 B17 RBD105

    For newest news you have to visit web and on the web I found this web site as a best website for hottest updates.

  5. 銆愩偄銉°儶銈獶YNAMICACCENTS銆戙儭銈ゃ儔銈ゃ兂USA銇噸鍘氥仾銉囥偠銈ゃ兂銇湪瑁姐儠銉笺儔銉溿偊銉紒銉€銈ゃ儕銉熴儍銈偄銈偦銉炽儎銆€12銈ゃ兂銉併儓銉笺儷銉€銉栥儷銉曘偅銉笺儉銉笺€€銉栥儵銉冦偗銆愩偛銉笺儓銆€銈点兗銈儷銆€銈便兗銈搞€€銉囥偠銈ゃ儕銉笺偤銆戔€汇亰鍙栥倞瀵勩仜

    If you desire to take a good deal from this piece of writing then you have to apply such techniques to your won weblog.

  6. 銆愬嵆绱嶃€戙€愬2銈岀瓔銆慘C’S 璨″竷 銈便兗銈枫兗銈?銉忋兗銉曘偊銈┿儸銉冦儓 銆?銉偠銉笺偊銈┿儸銉冦儓灏傞杸搴?銆戙€?閫佹枡鐒℃枡 銆戙€?鏃ユ湰瑁?銆戙€?闈╄病甯?銆?KC锛宻 銈便偆銈枫偆銈?銈ㄣ儸銉庛偄 銉曘儶銉笺偒銉冦儓 銉兂銈?銈︺偐銉儍銉?銉撱儷銉曘偐銉笺儔 [ 鏈潻 ][ 銈便兗銈枫兗銈?]

    Good way of telling, and pleasant paragraph to obtain facts concerning my presentation topic, which i am going to present in institution of higher education.

  7. 銆愬彈娉ㄧ敓鐢e晢鍝併€戝箙174cm 銉涖儻銈ゃ儓銈兗銈潗 銉涖儻銈ゃ儓銈兗銈劇鍨㈡潗 鏈ㄨ=銉曘儸銉笺儬銇儠銉偒銉愩兗銉兂銈般偨銉曘偂銉?鍥界敚銈姐儠銈?鏈ㄨ=銈姐儠銈?绡€鏈夋潗銈掍娇鐢ㄣ仐銇熻儗鏉裤亴榄呭姏鐨勩仾銉€銈ゃ儕銉熴儍銈仾3浜烘帥銇戙偨銉曘偂 ARKS-LS3P-NR 鈥?P銆?P銈傘偑銉笺儉銉煎彲鑳斤紒

    Howdy, i read your blog occasionally and i own a similar one and i was just curious if you get a lot of spam responses? If so how do you prevent it, any plugin or anything you can recommend? I get so much lately it’s driving me crazy so any support is…

  8. 銆愩偄銉°儶銈玏agwear銆慛Y銇汉姘椼儢銉┿兂銉夈倛銈婄洿杓稿叆銇笀澶仹銈偡銉c儸銇偔銉c儶銉笺儛銉冦偘銆傘儻銈般偊銈с偄銆€銈兗銉氥兂銈裤兗銉愩儍銈般偔銉c儶銈€€S銈点偆銈恒€€銉娿儊銉ャ儵銉椼儢銉┿儍銈€愩儦銉冦儓銈兗銉堛€€銈儯銉兗銉愩儍銈般€€鏃呰銆€銇婂嚭銇嬨亼銆戔€汇亰鍙栥倞瀵勩仜

    Write more, thats all I have to say. Literally, it seems as though you relied on the video to make your point. You clearly know what youre talking about, why waste your intelligence on just posting videos to your site when you could be giving us someth…

  9. 銆愬綋搴楀叏鍝丳10鍊?閫佹枡鐒℃枡!(1/3銇俱仹!)銆?銆?015骞寸鍐儮銉囥儷銈儶銈儵銉炽偣30锛匫FF銆?銈儍銉?銉儑銈c偣 闀疯 鑺辨焺 銉堛儵銉冦偗銈搞儯銈便儍銉?KG562KT61 銆愩亗銇欐ソ瀵惧繙銆?銈淬儷銉曘偊銈с偄 銆愩儩銈ゃ兂銉?0鍊?12/27 9:59銇俱仹)銆?10P24Dec15

    I’ve been surfing on-line more than 3 hours these days, but I by no means found any fascinating article like yours. It is pretty value sufficient for me. Personally, if all site owners and bloggers made excellent content as you did, the internet will…

  10. 涓夎彵閲嶅伐銆€FDTVP504HKAG4AG-CLS銆€ 2棣姏鐩稿綋銆€澶╀簳鍩嬭炯褰?鏂瑰悜鍚瑰嚭銇椼€€ 鍗樼浉200V銆€銉偆銉ゃ兗銉夈€€ 妤嫏鐢ㄣ偍銈偝銉炽€€hyper銆€ 銇婃巸闄ゃ儵銈儶銉笺儕銉戙儘銉€€ [鏃у瀷鐣細FDTVP504HK4-CLS]

    Hi, just wanted to say, I loved this post. It was helpful. Keep on posting!

  11. 銆愮壒渚°€戙€愩亗銇欐ソ瀵惧繙銆戦噹鐞冪敤鍝?銉兗銉兂銈般偣锛圧awlings锛?銆怗H4FGL44銆?纭紡鐢ㄣ偘銉┿儢锛堝唴閲庢墜鐢級 銉兗銉兂銈般偣銈层兗銉炪兗 銉┿偆銉?銆?0%OFF銆戙€愬湪搴嚘鍒嗐€?銈般儹銉笺儢 14FW

    No matter if some one searches for his vital thing, therefore he/she wishes to be available that in detail, therefore that thing is maintained over here.

  12. 銉忋兂銉併兂銈?甯藉瓙 銉┿偝銈广儐/銈偆銉撱兗銈儯銉冦儣銆€楣裤伄瀛愮法銇?銈广儩銉笺儐銈c兗 銉曘偂銉冦偡銉с兂/銈枫兂銉椼儷銉囥偠銈ゃ兂 銈点偆銈鸿鏁村彲/銉偆銉ゃ儷銉栥儷銉笺€愮敺鎬х敤銆戙€愬皬鐗┿€戙€愩儣銉偧銉炽儓銆戙€愩偖銉曘儓銆戙€愩偒銈搞儱銈儷銆戙€愰€佹枡鐒℃枡銆?(銇笺亞銇?甯藉瓙閫氳博 銉曘偂銉冦偡銉с兂 銇娿仐銈冦倢)

    Excellent post. I will be dealing with a few of these issues as well..

  13. 銆旂暀琚?銉兂銈裤儷銆曢粧鐣欒 銉曘儷銈汇儍銉堛儸銉炽偪銉?锛即-331 鐣欒銉兂銈裤儷 鐣欒 銉兂銈裤儷 璨歌。瑁?姝g倒 榛掔暀琚栥儸銉炽偪銉?榛掔暀銈佽 rental 绲愬寮?楂瀷 銉樸偄銈广偪銈ゃ儷 鐫€鐗┿儸銉炽偪銉?姣嶈Κ 鏈嶈 寰€寰╅€佹枡鐒℃枡

    I love your blog.. very nice colors & theme. Did you make this website yourself or did you hire someone to do it for you? Plz reply as I’m looking to create my own blog and would like to find out where u got this from. thanks

  14. 銆愰€佹枡鐒℃枡銆戙€愩亗銇欐ソ銆戙€?015鏄ュ鏂颁綔20%OFF銆戙偄銉儊銉撱偑 銉儑銈c兗銈?銉兂銉斻兗銈癸紙M锛?archivio 459514-140銆愩亗銇欐ソ_骞翠腑鐒′紤銆戙€愩亗銇欐ソ_鍦熸洔鍠舵キ銆戙€愩亗銇欐ソ_鏃ユ洔鍠舵キ銆?銆€銆?0P05Sep15銆戙€怱S_KO銆戙€怱S_WK銆戙€怱S_SH銆?10P20Nov15

    Good information. Lucky me I came across your website by chance (stumbleupon). I’ve book-marked it for later!

  15. 【正規品】 U-MA ウーマコンディショナー 300ml x 3セット 送料無料 (ZERO PLUS U-MA) 【激安 口コミ poff 送料込み セール サロン専売品】 U-MA ウーマ 馬油 育毛 頭皮 頭皮環境改善 ノンシリコン 02P19Dec15

    I’m not that much of a internet reader to be honest but your blogs really nice, keep it up! I’ll go ahead and bookmark your site to come back later on. Many thanks

  16. キャリーバッグ 機内持ち込み 3WAY ナイロンボストンキャリーバッグ 修学旅行 旅行用バッグ 旅行用かばん スーツケース ショルダーバッグ キャリーケース ボストンバッグ ビジネスバッグ 旅行 出張 帰省 2輪キャスター

    We’re a group of volunteers and opening a new scheme in our community. Your site provided us with valuable info to work on. You have done a formidable job and our whole community will be thankful to you.

  17. ブラックシリカネックレス(温かな波動で、心を癒す) 話題のジュエリー 「オプレ」  K18イエローゴールド【楽ギフ_包装】【楽ギフ_のし】【楽ギフ_のし宛書】【楽ギフ_メッセ入力】【smtb-kd】fs04gm

    We are a group of volunteers and opening a new scheme in our community. Your website offered us with valuable info to work on. You have done an impressive job and our whole community will be thankful to you.

  18. 【送料無料】【新軽量強化磁器製】【業務用食器・皿】【病院・福祉】【10個入】和らく煎茶 Mあじさい おかるのキモチ 美濃美人[83LWA10]

    Unquestionably believe that which you said. Your favorite reason seemed to be on the internet the simplest thing to be aware of. I say to you, I certainly get irked while people consider worries that they just do not know about. You managed to hit the…

  19. 【激安】Seashell SS-1 シーシェル デジタルカメラ水中ハウジング【YDKG-ms】【楽ギフ_包装】【送料無料】【smtb-MS】

    Hello there! I know this is kinda off topic however I’d figured I’d ask. Would you be interested in exchanging links or maybe guest authoring a blog post or vice-versa? My website covers a lot of the same subjects as yours and I think we could greatl…

  20. 【12月末まで!3900円お買い上げですぐに使える送料無料クーポン配布中※対象外有】Tory Burch トリーバーチ31129024 AMANDA DOUBLE SNAP WALLET アマンダ ダブルスナップウォレット001 BLACK ブラッゅ

    It’s great that you are getting ideas from this piece of writing as well as from our discussion made at this time.

  21. ラクレア オー シャンプーM&トリートメントA<2000ml×2000g業務用詰め替えセット>(しっとり&ふんわり) タマリス tamaris 02P20Nov15

    I like the helpful info you supply in your articles. I’ll bookmark your weblog and take a look at again right here frequently. I am slightly certain I will learn plenty of new stuff proper here! Good luck for the following!

Leave a Reply