Skip to content

Addressed issue causing the tour to break when a particular step is missing #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
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