hand-detection/assets/plugin/tour/tourist.js

1031 lines
28 KiB
JavaScript

(function() {
var _ref, _ref1, _ref2, _ref3,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
window.Tourist = window.Tourist || {};
/*
A model for the Tour. We'll only use the 'current_step' property.
*/
Tourist.Model = (function(_super) {
__extends(Model, _super);
function Model() {
_ref = Model.__super__.constructor.apply(this, arguments);
return _ref;
}
Model.prototype._module = 'Tourist';
return Model;
})(Backbone.Model);
window.Tourist.Tip = window.Tourist.Tip || {};
/*
The flyout showing the content of each step.
This is the base class containing most of the logic. Can extend for different
tooltip implementations.
*/
Tourist.Tip.Base = (function() {
Base.prototype._module = 'Tourist';
_.extend(Base.prototype, Backbone.Events);
Base.prototype.skipButtonTemplate = '<button class="btn btn-default btn-sm pull-right tour-next">Skip this step →</button>';
Base.prototype.nextButtonTemplate = '<button class="btn btn-primary btn-sm pull-right tour-next">Next step →</button>';
Base.prototype.finalButtonTemplate = '<button class="btn btn-primary btn-sm pull-right tour-next">Finish up</button>';
Base.prototype.closeButtonTemplate = '<a class="btn btn-close tour-close" href="#"><i class="icon icon-remove"></i></a>';
Base.prototype.okButtonTemplate = '<button class="btn btn-sm tour-close btn-primary">Okay</button>';
Base.prototype.actionLabelTemplate = _.template('<h4 class="action-label"><%= label %></h4>');
Base.prototype.actionLabels = ['Do this:', 'Then this:', 'Next this:'];
Base.prototype.highlightClass = 'tour-highlight';
Base.prototype.template = _.template('<div>\n <div class="tour-container">\n <%= close_button %>\n <%= content %>\n <p class="tour-counter <%= counter_class %>"><%= counter%></p>\n </div>\n <div class="tour-buttons">\n <%= buttons %>\n </div>\n</div>');
function Base(options) {
this.options = options != null ? options : {};
this.onClickNext = __bind(this.onClickNext, this);
this.onClickClose = __bind(this.onClickClose, this);
this.el = $('<div/>');
this.initialize(options);
this._bindClickEvents();
Tourist.Tip.Base._cacheTip(this);
}
Base.prototype.destroy = function() {
return this.el.remove();
};
Base.prototype.render = function(step) {
this.hide();
if (step) {
this._setTarget(step.target || false, step);
this._setZIndex('');
this._renderContent(step, this._buildContentElement(step));
if (step.target) {
this.show();
}
if (step.zIndex) {
this._setZIndex(step.zIndex, step);
}
}
return this;
};
Base.prototype.show = function() {};
Base.prototype.hide = function() {};
Base.prototype.setTarget = function(targetElement, step) {
return this._setTarget(targetElement, step);
};
Base.prototype.cleanupCurrentTarget = function() {
if (this.target && this.target.removeClass) {
this.target.removeClass(this.highlightClass);
}
return this.target = null;
};
/*
Event Handlers
*/
Base.prototype.onClickClose = function(event) {
this.trigger('click:close', this, event);
return false;
};
Base.prototype.onClickNext = function(event) {
this.trigger('click:next', this, event);
return false;
};
/*
Private
*/
Base.prototype._getTipElement = function() {};
Base.prototype._renderContent = function(step, contentElement) {};
Base.prototype._bindClickEvents = function() {
var el;
el = this._getTipElement();
el.delegate('.tour-close', 'click', this.onClickClose);
return el.delegate('.tour-next', 'click', this.onClickNext);
};
Base.prototype._setTarget = function(target, step) {
this.cleanupCurrentTarget();
if (target && step && step.highlightTarget) {
target.addClass(this.highlightClass);
}
return this.target = target;
};
Base.prototype._setZIndex = function(zIndex) {
var el;
el = this._getTipElement();
return el.css('z-index', zIndex || '');
};
Base.prototype._buildContentElement = function(step) {
var buttons, content;
buttons = this._buildButtons(step);
content = $($.parseHTML(this.template({
content: step.content,
buttons: buttons,
close_button: this._buildCloseButton(step),
counter: step.final ? '' : "step " + (step.index + 1) + " of " + step.total,
counter_class: step.final ? 'final' : ''
})));
if (!buttons) {
content.find('.tour-buttons').addClass('no-buttons');
}
this._renderActionLabels(content);
return content;
};
Base.prototype._buildButtons = function(step) {
var buttons;
buttons = '';
if (step.okButton) {
buttons += this.okButtonTemplate;
}
if (step.skipButton) {
buttons += this.skipButtonTemplate;
}
if (step.nextButton) {
buttons += step.final ? this.finalButtonTemplate : this.nextButtonTemplate;
}
return buttons;
};
Base.prototype._buildCloseButton = function(step) {
if (step.closeButton) {
return this.closeButtonTemplate;
} else {
return '';
}
};
Base.prototype._renderActionLabels = function(el) {
var action, actionIndex, actions, label, _i, _len, _results;
actions = el.find('.action');
actionIndex = 0;
_results = [];
for (_i = 0, _len = actions.length; _i < _len; _i++) {
action = actions[_i];
label = $($.parseHTML(this.actionLabelTemplate({
label: this.actionLabels[actionIndex]
})));
label.insertBefore(action);
_results.push(actionIndex++);
}
return _results;
};
Base._cacheTip = function(tip) {
if (!Tourist.Tip.Base._cachedTips) {
Tourist.Tip.Base._cachedTips = [];
}
return Tourist.Tip.Base._cachedTips.push(tip);
};
Base.destroy = function() {
var tip, _i, _len, _ref1;
if (!Tourist.Tip.Base._cachedTips) {
return;
}
_ref1 = Tourist.Tip.Base._cachedTips;
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
tip = _ref1[_i];
tip.destroy();
}
return Tourist.Tip.Base._cachedTips = null;
};
return Base;
})();
/*
Bootstrap based tip implementation
*/
Tourist.Tip.Bootstrap = (function(_super) {
__extends(Bootstrap, _super);
function Bootstrap() {
_ref1 = Bootstrap.__super__.constructor.apply(this, arguments);
return _ref1;
}
Bootstrap.prototype.initialize = function(options) {
var defs;
defs = {
showEffect: null,
hideEffect: null
};
this.options = _.extend(defs, options);
return this.tip = new Tourist.Tip.BootstrapTip(this.options);
};
Bootstrap.prototype.destroy = function() {
this.tip.destroy();
return Bootstrap.__super__.destroy.call(this);
};
Bootstrap.prototype.show = function() {
var fn;
if (this.options.showEffect) {
fn = Tourist.Tip.Bootstrap.effects[this.options.showEffect];
return fn.call(this, this.tip, this.tip.el);
} else {
return this.tip.show();
}
};
Bootstrap.prototype.hide = function() {
var fn;
if (this.options.hideEffect) {
fn = Tourist.Tip.Bootstrap.effects[this.options.hideEffect];
return fn.call(this, this.tip, this.tip.el);
} else {
return this.tip.hide();
}
};
/*
Private
*/
Bootstrap.prototype._getTipElement = function() {
return this.tip.el;
};
Bootstrap.prototype._setTarget = function(target, step) {
Bootstrap.__super__._setTarget.call(this, target, step);
return this.tip.setTarget(target);
};
Bootstrap.prototype._renderContent = function(step, contentElement) {
var at, my;
my = step.my || 'left center';
at = step.at || 'right center';
this.tip.setContainer(step.container || $('body'));
this.tip.setContent(contentElement);
return this.tip.setPosition(step.target || false, my, at);
};
return Bootstrap;
})(Tourist.Tip.Base);
Tourist.Tip.Bootstrap.effects = {
slidein: function(tip, element) {
var OFFSETS, css, easing, easings, offset, side, value, _i, _len;
OFFSETS = {
top: 80,
left: 80,
right: -80,
bottom: -80
};
side = tip.my.split(' ')[0];
side = side || 'top';
offset = OFFSETS[side];
if (side === 'bottom') {
side = 'top';
}
if (side === 'right') {
side = 'left';
}
value = parseInt(element.css(side));
element.stop();
css = {};
css[side] = value + offset;
element.css(css);
element.show();
css[side] = value;
easings = ['easeOutCubic', 'swing', 'linear'];
for (_i = 0, _len = easings.length; _i < _len; _i++) {
easing = easings[_i];
if ($.easing[easing]) {
break;
}
}
element.animate(css, 300, easing);
return null;
}
};
/*
Simple implementation of tooltip with bootstrap markup.
Almost entirely deals with positioning. Uses the similar method for
positioning as qtip2:
my: 'top center'
at: 'bottom center'
*/
Tourist.Tip.BootstrapTip = (function() {
BootstrapTip.prototype.template = '<div class="popover tourist-popover">\n <div class="arrow"></div>\n <div class="popover-content"></div>\n</div>';
BootstrapTip.prototype.FLIP_POSITION = {
bottom: 'top',
top: 'bottom',
left: 'right',
right: 'left'
};
function BootstrapTip(options) {
var defs;
defs = {
offset: 10,
tipOffset: 10
};
this.options = _.extend(defs, options);
this.el = $($.parseHTML(this.template));
this.hide();
}
BootstrapTip.prototype.destroy = function() {
return this.el.remove();
};
BootstrapTip.prototype.show = function() {
return this.el.show().addClass('visible');
};
BootstrapTip.prototype.hide = function() {
return this.el.hide().removeClass('visible');
};
BootstrapTip.prototype.setTarget = function(target) {
this.target = target;
return this._setPosition(this.target, this.my, this.at);
};
BootstrapTip.prototype.setPosition = function(target, my, at) {
this.target = target;
this.my = my;
this.at = at;
return this._setPosition(this.target, this.my, this.at);
};
BootstrapTip.prototype.setContainer = function(container) {
return container.append(this.el);
};
BootstrapTip.prototype.setContent = function(content) {
return this._getContentElement().html(content);
};
/*
Private
*/
BootstrapTip.prototype._getContentElement = function() {
return this.el.find('.popover-content');
};
BootstrapTip.prototype._getTipElement = function() {
return this.el.find('.arrow');
};
BootstrapTip.prototype._setPosition = function(target, my, at) {
var clas, css, originalDisplay, position, shift, targetPosition, tip, tipOffset, tipPosition, _ref2;
if (my == null) {
my = 'left center';
}
if (at == null) {
at = 'right center';
}
if (!target) {
return;
}
_ref2 = my.split(' '), clas = _ref2[0], shift = _ref2[1];
originalDisplay = this.el.css('display');
this.el.css({
top: 0,
left: 0,
margin: 0,
display: 'block'
}).removeClass('top').removeClass('bottom').removeClass('left').removeClass('right').addClass(this.FLIP_POSITION[clas]);
if (!target) {
return;
}
tip = this._getTipElement().css({
left: '',
right: '',
top: '',
bottom: ''
});
if (shift !== 'center') {
tipOffset = {
left: tip[0].offsetWidth / 2,
right: 0,
top: tip[0].offsetHeight / 2,
bottom: 0
};
css = {};
css[shift] = tipOffset[shift] + this.options.tipOffset;
css[this.FLIP_POSITION[shift]] = 'auto';
tip.css(css);
}
targetPosition = this._caculateTargetPosition(at, target);
tipPosition = this._caculateTipPosition(my, targetPosition);
position = this._adjustForArrow(my, tipPosition);
this.el.css(position);
return this.el.css({
display: originalDisplay
});
};
BootstrapTip.prototype._caculateTargetPosition = function(atPosition, target) {
var bounds, pos;
if (Object.prototype.toString.call(target) === '[object Array]') {
return {
left: target[0],
top: target[1]
};
}
bounds = this._getTargetBounds(target);
pos = this._lookupPosition(atPosition, bounds.width, bounds.height);
return {
left: bounds.left + pos[0],
top: bounds.top + pos[1]
};
};
BootstrapTip.prototype._caculateTipPosition = function(myPosition, targetPosition) {
var height, pos, width;
width = this.el[0].offsetWidth;
height = this.el[0].offsetHeight;
pos = this._lookupPosition(myPosition, width, height);
return {
left: targetPosition.left - pos[0],
top: targetPosition.top - pos[1]
};
};
BootstrapTip.prototype._adjustForArrow = function(myPosition, tipPosition) {
var clas, height, position, shift, tip, width, _ref2;
_ref2 = myPosition.split(' '), clas = _ref2[0], shift = _ref2[1];
tip = this._getTipElement();
width = tip[0].offsetWidth;
height = tip[0].offsetHeight;
position = {
top: tipPosition.top,
left: tipPosition.left
};
switch (clas) {
case 'top':
position.top += height + this.options.offset;
break;
case 'bottom':
position.top -= height + this.options.offset;
break;
case 'left':
position.left += width + this.options.offset;
break;
case 'right':
position.left -= width + this.options.offset;
}
switch (shift) {
case 'left':
position.left -= width / 2 + this.options.tipOffset;
break;
case 'right':
position.left += width / 2 + this.options.tipOffset;
break;
case 'top':
position.top -= height / 2 + this.options.tipOffset;
break;
case 'bottom':
position.top += height / 2 + this.options.tipOffset;
}
return position;
};
BootstrapTip.prototype._lookupPosition = function(position, width, height) {
var height2, posLookup, width2;
width2 = width / 2;
height2 = height / 2;
posLookup = {
'top left': [0, 0],
'left top': [0, 0],
'top right': [width, 0],
'right top': [width, 0],
'bottom left': [0, height],
'left bottom': [0, height],
'bottom right': [width, height],
'right bottom': [width, height],
'top center': [width2, 0],
'left center': [0, height2],
'right center': [width, height2],
'bottom center': [width2, height]
};
return posLookup[position];
};
BootstrapTip.prototype._getTargetBounds = function(target) {
var el, size;
el = target[0];
if (typeof el.getBoundingClientRect === 'function') {
size = el.getBoundingClientRect();
} else {
size = {
width: el.offsetWidth,
height: el.offsetHeight
};
}
return $.extend({}, size, target.offset());
};
return BootstrapTip;
})();
/*
Qtip based tip implementation
*/
Tourist.Tip.QTip = (function(_super) {
var ADJUST, OFFSETS, TIP_HEIGHT, TIP_WIDTH;
__extends(QTip, _super);
function QTip() {
this._renderTipBackground = __bind(this._renderTipBackground, this);
_ref2 = QTip.__super__.constructor.apply(this, arguments);
return _ref2;
}
TIP_WIDTH = 6;
TIP_HEIGHT = 14;
ADJUST = 10;
OFFSETS = {
top: 80,
left: 80,
right: -80,
bottom: -80
};
QTip.prototype.QTIP_DEFAULTS = {
content: {
text: ' '
},
show: {
ready: false,
delay: 0,
effect: function(qtip) {
var css, el, offset, side, value;
el = $(this);
side = qtip.options.position.my;
if (side) {
side = side[side.precedance];
}
side = side || 'top';
offset = OFFSETS[side];
if (side === 'bottom') {
side = 'top';
}
if (side === 'right') {
side = 'left';
}
value = parseInt(el.css(side));
css = {};
css[side] = value + offset;
el.css(css);
el.show();
css[side] = value;
el.animate(css, 300, 'easeOutCubic');
return null;
},
autofocus: false
},
hide: {
event: null,
delay: 0,
effect: false
},
position: {
adjust: {
method: 'shift shift',
scroll: false
}
},
style: {
classes: 'ui-tour-tip',
tip: {
height: TIP_WIDTH,
width: TIP_HEIGHT
}
},
events: {},
zindex: 2000
};
QTip.prototype.initialize = function(options) {
options = $.extend(true, {}, this.QTIP_DEFAULTS, options);
this.el.qtip(options);
this.qtip = this.el.qtip('api');
return this.qtip.render();
};
QTip.prototype.destroy = function() {
if (this.qtip) {
this.qtip.destroy();
}
return QTip.__super__.destroy.call(this);
};
QTip.prototype.show = function() {
return this.qtip.show();
};
QTip.prototype.hide = function() {
return this.qtip.hide();
};
/*
Private
*/
QTip.prototype._getTipElement = function() {
return $('#qtip-' + this.qtip.id);
};
QTip.prototype._setTarget = function(targetElement, step) {
QTip.__super__._setTarget.call(this, targetElement, step);
return this.qtip.set('position.target', targetElement || false);
};
QTip.prototype._renderContent = function(step, contentElement) {
var at, my,
_this = this;
my = step.my || 'left center';
at = step.at || 'right center';
this._adjustPlacement(my, at);
this.qtip.set('content.text', contentElement);
this.qtip.set('position.container', step.container || $('body'));
this.qtip.set('position.my', my);
this.qtip.set('position.at', at);
this.qtip.set('position.viewport', step.viewport || false);
this.qtip.set('position.target', step.target || false);
return setTimeout(function() {
return _this._renderTipBackground(my.split(' ')[0]);
}, 10);
};
QTip.prototype._adjustPlacement = function(my, at) {
if (my.indexOf('top') === 0) {
return this._adjust(0, ADJUST);
} else if (my.indexOf('bottom') === 0) {
return this._adjust(0, -ADJUST);
} else if (my.indexOf('right') === 0) {
return this._adjust(-ADJUST, 0);
} else {
return this._adjust(ADJUST, 0);
}
};
QTip.prototype._adjust = function(adjustX, adjusty) {
this.qtip.set('position.adjust.x', adjustX);
return this.qtip.set('position.adjust.y', adjusty);
};
QTip.prototype._renderTipBackground = function(direction) {
var bg, el;
el = $('#qtip-' + this.qtip.id + ' .qtip-tip');
bg = el.find('.qtip-tip-bg');
if (!bg.length) {
bg = $('<div/>', {
'class': 'icon icon-tip qtip-tip-bg'
});
el.append(bg);
}
bg.removeClass('top left right bottom');
return bg.addClass(direction);
};
return QTip;
})(Tourist.Tip.Base);
/*
Simplest implementation of a tooltip. Used in the tests. Useful as an example
as well.
*/
Tourist.Tip.Simple = (function(_super) {
__extends(Simple, _super);
function Simple() {
_ref3 = Simple.__super__.constructor.apply(this, arguments);
return _ref3;
}
Simple.prototype.initialize = function(options) {
return $('body').append(this.el);
};
Simple.prototype.show = function() {
return this.el.show();
};
Simple.prototype.hide = function() {
return this.el.hide();
};
Simple.prototype._getTipElement = function() {
return this.el;
};
Simple.prototype._renderContent = function(step, contentElement) {
return this.el.html(contentElement);
};
return Simple;
})(Tourist.Tip.Base);
/*
A way to make a tour. Basically, you specify a series of steps which explain
elements to point at and what to say. This class manages moving between those
steps.
The 'step object' is a simple js obj that specifies how the step will behave.
A simple Example of a step object:
{
content: '<p>Welcome to my step</p>'
target: $('#something-to-point-at')
closeButton: true
highlightTarget: true
setup: (tour, options) ->
# do stuff in the interface/bind
teardown: (tour, options) ->
# remove stuff/unbind
}
Basic Step object options:
content - a string of html to put into the step.
target - jquery object or absolute point: [10, 30]
highlightTarget - optional bool, true will outline the target with a bright color.
container - optional jquery element that should contain the step flyout.
default: $('body')
viewport - optional jquery element that the step flyout should stay within.
$(window) is commonly used. default: false
my - string position of the pointer on the tip. default: 'left center'
at - string position on the element the tip points to. default: 'right center'
see http://craigsworks.com/projects/qtip2/docs/position/#basics
Step object button options:
okButton - optional bool, true will show a red ok button
closeButton - optional bool, true will show a grey close button
skipButton - optional bool, true will show a grey skip button
nextButton - optional bool, true will show a red next button
Step object function options:
All functions on the step will have the signature '(tour, options) ->'
tour - the Draw.Tour object. Handy to call tour.next()
options - the step options. An object passed into the tour when created.
It has the environment that the fns can use to manipulate the
interface, bind to events, etc. The same object is passed to all
of a step object's functions, so it is handy for passing data
between steps.
setup - called before step is shown. Use to scroll to your target, hide/show things, ...
'this' is the step object itself.
MUST return an object. Properties in the returned object will override
properties in the step object.
i.e. the target might be dynamic so you would specify:
setup: (tour, options) ->
return { target: $('#point-to-me') }
teardown - function called right before hiding the step. Use to unbind from
things you bound to in setup().
'this' is the step object itself.
Return nothing.
bind - an array of function names to bind. Use this for event handlers you use in setup().
Will bind functions to the step object as this, and the first 2 args as tour and options.
i.e.
bind: ['onChangeSomething']
setup: (tour, options) ->
options.document.bind('change:something', @onChangeSomething)
onChangeSomething: (tour, options, model, value) ->
tour.next()
teardown: (tour, options) ->
options.document.unbind('change:something', @onChangeSomething)
*/
Tourist.Tour = (function() {
_.extend(Tour.prototype, Backbone.Events);
function Tour(options) {
var defs, tipOptions;
this.options = options != null ? options : {};
this.onChangeCurrentStep = __bind(this.onChangeCurrentStep, this);
this.next = __bind(this.next, this);
defs = {
tipClass: 'Bootstrap'
};
this.options = _.extend(defs, this.options);
this.model = new Tourist.Model({
current_step: null
});
tipOptions = _.extend({
model: this.model
}, this.options.tipOptions);
this.view = new Tourist.Tip[this.options.tipClass](tipOptions);
this.view.bind('click:close', _.bind(this.stop, this, true));
this.view.bind('click:next', this.next);
this.model.bind('change:current_step', this.onChangeCurrentStep);
}
/*
Public
*/
Tour.prototype.start = function() {
this.trigger('start', this);
return this.next();
};
Tour.prototype.stop = function(doFinalStep) {
if (doFinalStep) {
return this._showCancelFinalStep();
} else {
return this._stop();
}
};
Tour.prototype.next = function() {
var currentStep, index;
currentStep = this._teardownCurrentStep();
index = 0;
if (currentStep) {
index = currentStep.index + 1;
}
if (index < this.options.steps.length) {
return this._showStep(this.options.steps[index], index);
} else if (index === this.options.steps.length) {
return this._showSuccessFinalStep();
} else {
return this._stop();
}
};
Tour.prototype.setStepOptions = function(stepOptions) {
return this.options.stepOptions = stepOptions;
};
/*
Handlers
*/
Tour.prototype.onChangeCurrentStep = function(model, step) {
return this.view.render(step);
};
/*
Private
*/
Tour.prototype._showCancelFinalStep = function() {
return this._showFinalStep(false);
};
Tour.prototype._showSuccessFinalStep = function() {
return this._showFinalStep(true);
};
Tour.prototype._teardownCurrentStep = function() {
var currentStep;
currentStep = this.model.get('current_step');
this._teardownStep(currentStep);
return currentStep;
};
Tour.prototype._stop = function() {
this._teardownCurrentStep();
this.model.set({
current_step: null
});
return this.trigger('stop', this);
};
Tour.prototype._showFinalStep = function(success) {
var currentStep, finalStep;
currentStep = this._teardownCurrentStep();
finalStep = success ? this.options.successStep : this.options.cancelStep;
if (_.isFunction(finalStep)) {
finalStep.call(this, this, this.options.stepOptions);
finalStep = null;
}
if (!finalStep) {
return this._stop();
}
if (currentStep && currentStep.final) {
return this._stop();
}
finalStep.final = true;
return this._showStep(finalStep, this.options.steps.length);
};
Tour.prototype._showStep = function(step, index) {
if (!step) {
return;
}
step = _.clone(step);
step.index = index;
step.total = this.options.steps.length;
if (!step.final) {
step.final = this.options.steps.length === index + 1 && !this.options.successStep;
}
step = _.extend(step, this._setupStep(step));
return this.model.set({
current_step: step
});
};
Tour.prototype._setupStep = function(step) {
var fn, _i, _len, _ref4;
if (!(step && step.setup)) {
return {};
}
if (step.bind) {
_ref4 = step.bind;
for (_i = 0, _len = _ref4.length; _i < _len; _i++) {
fn = _ref4[_i];
step[fn] = _.bind(step[fn], step, this, this.options.stepOptions);
}
}
return step.setup.call(step, this, this.options.stepOptions) || {};
};
Tour.prototype._teardownStep = function(step) {
if (step && step.teardown) {
step.teardown.call(step, this, this.options.stepOptions);
}
return this.view.cleanupCurrentTarget();
};
return Tour;
})();
}).call(this);