Skip to content
264 changes: 224 additions & 40 deletions bootstro.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,9 @@
* Revealing Module Pattern from
* http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
*
* Bootstrap popover variable width workaround
* Bootstrap popover variable width
* http://stackoverflow.com/questions/10028218/twitter-bootstrap-popovers-multiple-widths-and-other-css-properties
*
* Lisence : MIT . See accompanied LICENSE file.
*
*/

$(document).ready(function(){
Expand All @@ -19,38 +18,143 @@ $(document).ready(function(){
var count;
var popovers = []; //contains array of the popovers data
var activeIndex = null; //index of active item
var defaultOrder = true;
// if true the DOM order is followed. it is changed to false when step index is given for atleast one element.
// refer line #322

var defaults = {
nextButton : '<button class="btn btn-primary btn-mini bootstro-next-btn">Next &raquo;</button>',
prevButton : '<button class="btn btn-primary btn-mini bootstro-prev-btn">&laquo; Prev</button>',
finishButton : '<button class="btn btn-mini btn-success bootstro-finish-btn"><i class="icon-ok"></i> Ok I got it, get back to the site</button>',
nextButtonText : 'Next &raquo;', //will be wrapped with button as below
//nextButton : '<button class="btn btn-primary btn-mini bootstro-next-btn">Next &raquo;</button>',
prevButtonText : '&laquo; Prev',
//prevButton : '<button class="btn btn-primary btn-mini bootstro-prev-btn">&laquo; Prev</button>',
finishButtonText : '<i class="icon-ok"></i> Ok I got it, get back to the site',
//finishButton : '<button class="btn btn-mini btn-success bootstro-finish-btn"><i class="icon-ok"></i> Ok I got it, get back to the site</button>',
stopOnBackdropClick : true,
stopOnEsc : true
stopOnEsc : true,

//onComplete : function(params){} //params = {idx : activeIndex}
//onExit : function(params){} //params = {idx : activeIndex}
//onStep : function(params){} //params = {idx : activeIndex, direction : [next|prev]}
//url : String // ajaxed url to get show data from

margin : 100, //if the currently shown element's margin is less than this value
// the element should be scrolled so that i can be viewed properly. This is useful
// for sites which have fixed top/bottom nav bar
};
var settings;


//===================PRIVATE METHODS======================
//http://stackoverflow.com/questions/487073/check-if-element-is-visible-after-scrolling
function is_entirely_visible($elem)
{
var docViewTop = $(window).scrollTop();
var docViewBottom = docViewTop + $(window).height();

var elemTop = $elem.offset().top;
var elemBottom = elemTop + $elem.height();

return ((elemBottom >= docViewTop) && (elemTop <= docViewBottom)
&& (elemBottom <= docViewBottom) && (elemTop >= docViewTop) );
}

//add the nav buttons to the popover content;

function add_nav_btn(content, i)
{
count = $elements.size();
var $el = get_element(i);
var nextButton, prevButton, finishButton;

content = content + "<div class='bootstro-nav-wrapper'>";
if ($el.attr('data-bootstro-nextButton'))
{
nextButton = $el.attr('data-bootstro-nextButton');
}
else if ( $el.attr('data-bootstro-nextButtonText') )
{
nextButton = '<button class="btn btn-primary btn-mini bootstro-next-btn">' + $el.attr('data-bootstro-nextButtonText') + '</button>';
}
else
{
if (typeof settings.nextButton != 'undefined' /*&& settings.nextButton != ''*/)
nextButton = settings.nextButton;
else
nextButton = '<button class="btn btn-primary btn-mini bootstro-next-btn">' + settings.nextButtonText + '</button>';
}

if ($el.attr('data-bootstro-prevButton'))
{
prevButton = $el.attr('data-bootstro-prevButton');
}
else if ( $el.attr('data-bootstro-prevButtonText') )
{
prevButton = '<button class="btn btn-primary btn-mini bootstro-prev-btn">' + $el.attr('data-bootstro-prevButtonText') + '</button>';
}
else
{
if (typeof settings.prevButton != 'undefined' /*&& settings.prevButton != ''*/)
prevButton = settings.prevButton;
else
prevButton = '<button class="btn btn-primary btn-mini bootstro-prev-btn">' + settings.prevButtonText + '</button>';
}

if ($el.attr('data-bootstro-finishButton'))
{
finishButton = $el.attr('data-bootstro-finishButton');
}
else if ( $el.attr('data-bootstro-finishButtonText') )
{
finishButton = '<button class="btn btn-primary btn-mini bootstro-finish-btn">' + $el.attr('data-bootstro-finishButtonText') + '</button>';
}
else
{
if (typeof settings.finishButton != 'undefined' /*&& settings.finishButton != ''*/)
finishButton = settings.finishButton;
else
finishButton = '<button class="btn btn-primary btn-mini bootstro-finish-btn">' + settings.finishButtonText + '</button>';
}


if (count != 1)
{
if (i == 0)
content = content + settings.nextButton;
content = content + nextButton;
else if (i == count -1 )
content = content + settings.prevButton;
content = content + prevButton;
else
content = content + settings.nextButton + settings.prevButton
content = content + nextButton + prevButton
}
content = content + '</div>';

content = content +'<div class="bootstro-finish-btn-wrapper">' + settings.finishButton + '</div>';
content = content +'<div class="bootstro-finish-btn-wrapper">' + finishButton + '</div>';
return content;
}

nextIndex = function(indexToTry){
closestIndex = null
// loop and find the next available value less than or equal to the indexToTry
$.each(indexes, function(){
if (parseInt(this) >= parseInt(indexToTry)) {
closestIndex = this;
return false;
}
});
return closestIndex;
}

prevIndex = function(indexToTry){
closestIndex = null
reverseIndexes = $.makeArray(indexes).reverse()
// loop and find the previous available value less than or equal to the indexToTry
$.each(reverseIndexes, function(){
if (parseInt(this) <= parseInt(indexToTry)) {
closestIndex = this;
return false;
}
});
return closestIndex;
}


//get the element to intro at stack i
get_element = function(i)
Expand All @@ -72,16 +176,20 @@ $(document).ready(function(){
*/
}
}

getStepCount = function(i){
return defaultOrder ? i : $.inArray(parseInt(i), indexes)
}

get_popup = function(i)
{
var p = {};
$el = get_element(i);
var $el = get_element(i);
//p.selector = selector;
var t = '';
if (count > 1)
{
t = "<span class='label label-success'>" + (i +1) + "/" + count + "</span>";
t = "<span class='label label-success'>" + (getStepCount(i)+1) + "/" + count + "</span>";
}
p.title = $el.attr('data-bootstro-title') || '';
if (p.title != '' && t != '')
Expand Down Expand Up @@ -120,10 +228,10 @@ $(document).ready(function(){
//destroy popover at stack index i
bootstro.destroy_popover = function(i)
{
i = i || 0;
var i = i || 0;
if (i != 'all')
{
$el = get_element(i);//$elements.eq(i);
var $el = get_element(i);//$elements.eq(i);
$el.popover('destroy').removeClass('bootstro-highlight');
}
/*
Expand All @@ -143,65 +251,141 @@ $(document).ready(function(){
bootstro.destroy_popover(activeIndex);
bootstro.unbind();
$("div.bootstro-backdrop").remove();
if (typeof settings.onExit == 'function')
settings.onExit.call(this,{idx : getStepCount(activeIndex)});
};


//go to the popover number idx starting from 0
bootstro.go_to = function(idx)
{
//destroy current popover if any
bootstro.destroy_popover(activeIndex);
if (count != 0)
{
p = get_popup(idx);
$el = get_element(idx);
var p = get_popup(idx);
var $el = get_element(idx);

$el.popover(p).popover('show');

min = Math.min($(".popover.in").offset().top, $el.offset().top);
$('html,body').animate({
scrollTop: min - 20},
'slow');
//scroll if neccessary
var docviewTop = $(window).scrollTop();
var top = Math.min($(".popover.in").offset().top, $el.offset().top);

//distance between docviewTop & min.
var topDistance = top - docviewTop;

if (topDistance < settings.margin) //the element too up above
$('html,body').animate({
scrollTop: top - settings.margin},
'slow');
else if(!is_entirely_visible($(".popover.in")) || !is_entirely_visible($el))
//the element is too down below
$('html,body').animate({
scrollTop: top - settings.margin},
'slow');
// html

$el.addClass('bootstro-highlight');
activeIndex = idx;
}
};

bootstro.next = function()
{
if (activeIndex + 1 == count)
indexToEnd = defaultOrder ? count-1 : indexes.get(-1)
if (activeIndex == indexToEnd)
{
alert('End of introduction');
if (typeof settings.onComplete == 'function')
settings.onComplete.call(this, {idx : getStepCount(activeIndex)});//
}
else
bootstro.go_to(activeIndex + 1);
{
// bootstro.go_to(activeIndex + 1);
defaultOrder ? bootstro.go_to(activeIndex + 1) : bootstro.go_to(nextIndex(activeIndex + 1));
if (typeof settings.onStep == 'function')
settings.onStep.call(this, {idx : activeIndex, direction : 'next'});//
}
};

bootstro.prev = function()
{
if (activeIndex == 0)
indexToEnd = defaultOrder ? 0 : indexes.get(0)
if (activeIndex == indexToEnd)
{
alert('At start of intros');
/*
if (typeof settings.onRewind == 'function')
settings.onRewind.call(this, {idx : activeIndex, direction : 'prev'});//
*/
}
else
{
defaultOrder ? bootstro.go_to(activeIndex - 1) : bootstro.go_to(prevIndex(activeIndex - 1));
if (typeof settings.onStep == 'function')
settings.onStep.call(this, {idx : activeIndex, direction : 'prev'});//
}
};

bootstro._start = function(selector)
{
selector = selector || '.bootstro';

$elements = $(selector);
count = $elements.size();
if (count > 0 && $('div.bootstro-backdrop').length === 0)
{
// Prevents multiple copies
$('<div class="bootstro-backdrop"></div>').appendTo('body');
bootstro.bind();

indexes = $elements.map(function(){ return parseInt($(this).attr('data-bootstro-step')) })
defaultOrder = $.grep(indexes, function(x){ return !(isNaN(x)) }).length == 0 ? true : false
// set defaultOrder to true inorder to follow DOM order when all the elements are not provided with data-bootstro-step attr

if (!defaultOrder)
indexes = indexes.sort(function(a, b){ return a - b })

defaultOrder ? bootstro.go_to(0) : bootstro.go_to(nextIndex(0));
// bootstro.go_to(0);
}
else
bootstro.go_to(activeIndex -1);
};

bootstro.start = function(selector, options)
{

settings = $.extend(true, {}, defaults); //deep copy
//TODO: if options specifies a URL, get the intro text array from URL
$.extend(settings, options || {});
//if options specifies a URL, get the intro configuration from URL via ajax
if (typeof settings.url != 'undefined')
{
//get config from ajax
$.ajax({
url : settings.url,
success : function(data){
if (data.success)
{
//result is an array of {selector:'','title':'','width', ...}
var popover = data.result;
//console.log(popover);
var selectorArr = [];
$.each(popover, function(t,e){
//only deal with the visible element
//build the selector
$.each(e, function(j, attr){
$(e.selector).attr('data-bootstro-' + j, attr);
});
if ($(e.selector).is(":visible"))
selectorArr.push(e.selector);
});
selector = selectorArr.join(",");
bootstro._start(selector);
}
}
});
}
else
{
bootstro._start(selector);
}

selector = selector || '.bootstro';
$elements = $(selector);
count = $elements.size();

$('<div class="bootstro-backdrop"></div>').appendTo('body');
bootstro.bind();
bootstro.go_to(0);
};

//bind the nav buttons click event
Expand Down Expand Up @@ -253,4 +437,4 @@ $(document).ready(function(){
}

}( window.bootstro = window.bootstro || {}, jQuery ));
});
});
1 change: 1 addition & 0 deletions bootstro.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"success":true,"result":[{"selector":"#demo_stopOn","title":"Ajaxed title from server","content":"I was found because I have selector=#demo_stopOn","width":"400px","placement":"right"},{"selector":"#demo_ajax","title":"Ajaxed Title 2","content":"I was found because I have selector=#demo_ajax","width":"400px","placement":"right"}]}
Loading