From 38440a9f3e493b3f54cf2bbf5f1c0572917b977d Mon Sep 17 00:00:00 2001 From: IGreatlyDislikeJavascript <45369719+IGreatlyDislikeJavascript@users.noreply.github.com> Date: Wed, 28 Nov 2018 17:17:28 +0000 Subject: [PATCH] Init fixed. Scroll improved. onElementUnavailable added. Progress bar added Fixed flow issue with tour.init() especially where page contains hidden elements - Do not call tour.init(). Create your new Tour({options}); then call Tour.start or .restart when you want to show the tour. (https://github.com/sorich87/bootstrap-tour/issues/700 Fixed inefficient display of tour popover - popovers do not constantly reload current step on scroll. Flickering/flashing resolved. (https://github.com/sorich87/bootstrap-tour/issues/685) - element popovers don't move around the page as you scroll - this is a personal thing, but I didn't like the functionality where if you scroll the page so the element is no longer visible, the popover follows you around. Doesn't make much sense because the popover isn't visually attached to it's parent element. - orphan popovers stay stuck to the center of the screen no matter what Added onElementUnavailable(tour, step) to global and step options - Specify a function as normal (i.e.: same as onNext etc) with pattern above. Function will be called when the element of the step is unavailable == missing, hidden etc. "step" parameter indicates the step that is going to be skipped. Added progress indicators and ability to dynamically change during tour: - Global option "showProgressBar" : set to true to show a (bootstrap) progress bar, or false to hide it - Step option "showProgressBar" will override global option, so you can turn on/off progress bars per step Customise the progress bar using option getProgressBarHTML: getProgressBarHTML: function(percentProgress) { return '
'} - Global and step option "showProgressText" shows a "N / X" tour step counter in the popover title bar Customise the text using option getProgressTextHTML: getProgressBarHTML: function(stepNumber, percentProgress, stepCount) { return '' + stepNumber + '/' + stepCount + ''; } Based on https://github.com/sorich87/bootstrap-tour/issues/362 --- build/js/bootstrap-tour.js | 187 ++++++++++++++++++++++++++----------- 1 file changed, 135 insertions(+), 52 deletions(-) diff --git a/build/js/bootstrap-tour.js b/build/js/bootstrap-tour.js index 7be53ed7..61682ad9 100644 --- a/build/js/bootstrap-tour.js +++ b/build/js/bootstrap-tour.js @@ -67,6 +67,10 @@ var bind = function (fn, me) { delay: false, basePath: '', template: '', + showProgressBar: true, + showProgressText: true, + getProgressBarHTML: null,//function(percent) {}, + getProgressTextHTML: null,//function(stepNumber, percent, stepCount) {}, afterSetState: function (key, value) {}, afterGetState: function (key, value) {}, afterRemoveState: function (key) {}, @@ -80,7 +84,8 @@ var bind = function (fn, me) { onPrev: function (tour) {}, onPause: function (tour, duration) {}, onResume: function (tour, duration) {}, - onRedirectError: function (tour) {} + onRedirectError: function (tour) {}, + onElementUnavailable: function (tour, stepNumber) {} }, options); this._force = false; this._inited = false; @@ -104,6 +109,10 @@ var bind = function (fn, me) { this._options.steps.push(step); return this; }; + + Tour.prototype.getStepCount = function() { + return this._options.steps.length; + } Tour.prototype.getStep = function (i) { if (this._options.steps[i] != null) { @@ -133,6 +142,10 @@ var bind = function (fn, me) { duration: this._options.duration, delay: this._options.delay, template: this._options.template, + showProgressBar: this._options.showProgressBar, + showProgressText: this._options.showProgressText, + getProgressBarHTML: this._options.getProgressBarHTML, + getProgressTextHTML: this._options.getProgressTextHTML, onShow: this._options.onShow, onShown: this._options.onShown, onHide: this._options.onHide, @@ -141,7 +154,8 @@ var bind = function (fn, me) { onPrev: this._options.onPrev, onPause: this._options.onPause, onResume: this._options.onResume, - onRedirectError: this._options.onRedirectError + onRedirectError: this._options.onRedirectError, + onElementUnavailable: this._options.onElementUnavailable }, this._options.steps[i]); } }; @@ -165,10 +179,13 @@ var bind = function (fn, me) { return _this._showPopoverAndOverlay(_this._current); }; })(this)); - if (this._current !== null) { +/* + // Removed - .init is an unnecessary call, .start force calls .init if tour is not initialized. This code creates conflict + // where page has hidden elements, page is reloaded, tour is then forced to start on step N when hidden element is not shown yet + if (this._current !== null) { this.showStep(this._current); } - this._inited = true; + */ this._inited = true; return this; }; @@ -180,10 +197,13 @@ var bind = function (fn, me) { if (!this._inited) { this.init(force); } - if (this._current === null) { + + // removed if condition - tour should always start when .start is called. Original flow prevented tour from start if _current step index was set (tour already started) + //if (this._current === null) { promise = this._makePromise(this._options.onStart != null ? this._options.onStart(this) : void 0); this._callOnPromiseDone(promise, this.showStep, 0); - } + // } + return this; }; @@ -364,8 +384,15 @@ var bind = function (fn, me) { showStepHelper = (function (_this) { return function (e) { if (_this._isOrphan(step)) { - if (step.orphan === false) { + if (step.orphan === false) + { _this._debug("Skip the orphan step " + (_this._current + 1) + ".\nOrphan option is false and the element does not exist or is hidden."); + + if(typeof(step.onElementUnavailable) == "function") + { + step.onElementUnavailable(_this, _this._current + 1); + } + if (skipToPrevious) { _this._showPrevStep(); } else { @@ -621,7 +648,7 @@ var bind = function (fn, me) { if (step.onShown != null) { step.onShown(this); } - return this._debug("Step " + (this._current + 1) + " of " + this._options.steps.length); + return this; }; Tour.prototype._showPopover = function (step, i) { @@ -629,55 +656,106 @@ var bind = function (fn, me) { $tip, isOrphan, options, - shouldAddSmart; - $(".tour-" + this._options.name).remove(); - options = $.extend({}, this._options); - isOrphan = this._isOrphan(step); - step.template = this._template(step, i); - if (isOrphan) { - step.element = 'body'; - step.placement = 'top'; - } - $element = $(step.element); - $element.addClass("tour-" + this._options.name + "-element tour-" + this._options.name + "-" + i + "-element"); - if (step.options) { - $.extend(options, step.options); - } - if (step.reflex && !isOrphan) { - $(step.reflexElement).addClass('tour-step-element-reflex').off((this._reflexEvent(step.reflex)) + ".tour-" + this._options.name).on((this._reflexEvent(step.reflex)) + ".tour-" + this._options.name, (function (_this) { - return function () { - if (_this._isLast()) { - return _this.next(); - } else { - return _this.end(); - } - }; - })(this)); + shouldAddSmart, + title, + content, + percentProgress; + + if($(document).find(".popover.tour-" + this._options.name + ".tour-" + this._options.name + "-" + i).length == 0) + { + $(".tour-" + this._options.name).remove(); + options = $.extend({}, this._options); + isOrphan = this._isOrphan(step); + step.template = this._template(step, i); + if (isOrphan) { + step.element = 'body'; + step.placement = 'top'; + } + $element = $(step.element); + $element.addClass("tour-" + this._options.name + "-element tour-" + this._options.name + "-" + i + "-element"); + if (step.options) { + $.extend(options, step.options); + } + if (step.reflex && !isOrphan) { + $(step.reflexElement).addClass('tour-step-element-reflex').off((this._reflexEvent(step.reflex)) + ".tour-" + this._options.name).on((this._reflexEvent(step.reflex)) + ".tour-" + this._options.name, (function (_this) { + return function () { + if (_this._isLast()) { + return _this.next(); + } else { + return _this.end(); + } + }; + })(this)); + } + + shouldAddSmart = step.smartPlacement === true && step.placement.search(/auto/i) === -1; + + title = step.title; + content = step.content; + percentProgress = parseInt(((i + 1) / this.getStepCount()) * 100); + + if(step.showProgressBar) + { + if(typeof(step.getProgressBarHTML) == "function") + { + content = step.getProgressBarHTML(percentProgress) + content; + } + else + { + content = '
' + content; + } + } + + if(step.showProgressText) + { + if(typeof(step.getProgressTextHTML) == "function") + { + title += step.getProgressTextHTML(i, percentProgress, this.getStepCount()); + } + else + { + title += '' + (i + 1) + '/' + this.getStepCount() + ''; + } + } + + $element.popover({ + placement: shouldAddSmart ? "auto " + step.placement : step.placement, + trigger: 'manual', + title: title, + content: content, + html: true, + animation: step.animation, + container: step.container, + template: step.template, + selector: step.element + }).popover('show'); + + $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip(); + $tip.attr('id', step.id); + if ($element.css('position') === 'fixed') { + $tip.css('position', 'fixed'); + } + + this._debug("Step " + (this._current + 1) + " of " + this._options.steps.length); } + else + { + if (this._isOrphan(step)) { + step.element = 'body'; + step.placement = 'top'; + } - shouldAddSmart = step.smartPlacement === true && step.placement.search(/auto/i) === -1; - - $element.popover({ - placement: shouldAddSmart ? "auto " + step.placement : step.placement, - trigger: 'manual', - title: step.title, - content: step.content, - html: true, - animation: step.animation, - container: step.container, - template: step.template, - selector: step.element - }).popover('show'); - - $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip(); - $tip.attr('id', step.id); - if ($element.css('position') === 'fixed') { - $tip.css('position', 'fixed'); + $element = $(step.element); + $tip = $element.data('bs.popover') ? $element.data('bs.popover').tip() : $element.data('popover').tip(); } - this._reposition($tip, step); + if (isOrphan) { return this._center($tip); } + else + { + return this._reposition($tip, step); + } }; Tour.prototype._template = function (step, i) { @@ -738,13 +816,16 @@ var bind = function (fn, me) { tipOffset; offsetWidth = $tip[0].offsetWidth; offsetHeight = $tip[0].offsetHeight; + tipOffset = $tip.offset(); originalLeft = tipOffset.left; originalTop = tipOffset.top; - offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight(); + + offsetBottom = $(document).height() - tipOffset.top - $tip.outerHeight(); if (offsetBottom < 0) { tipOffset.top = tipOffset.top + offsetBottom; } + offsetRight = $('html').outerWidth() - tipOffset.left - $tip.outerWidth(); if (offsetRight < 0) { tipOffset.left = tipOffset.left + offsetRight; @@ -755,7 +836,9 @@ var bind = function (fn, me) { if (tipOffset.left < 0) { tipOffset.left = 0; } + $tip.offset(tipOffset); + if (step.placement === 'bottom' || step.placement === 'top') { if (originalLeft !== tipOffset.left) { return this._replaceArrow($tip, (tipOffset.left - originalLeft) * 2, offsetWidth, 'left');