Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/TypeScript/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
TODO: Add TS instructions
206 changes: 206 additions & 0 deletions app/TypeScript/angular-ui-tour-backdrop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
module Tour {
export class TourBackdrop {
private $body: ng.IRootElementService;
private viewWindow: { top: ng.IRootElementService, bottom: ng.IRootElementService, left: ng.IRootElementService, right: ng.IRootElementService, target: ng.IRootElementService };

private preventDefault(e) {
e.preventDefault();
}

private preventScrolling() {
this.$body.addClass('no-scrolling');
this.$body.on('touchmove', this.preventDefault);
}

private allowScrolling() {
this.$body.removeClass('no-scrolling');
this.$body.off('touchmove', this.preventDefault);
}

private createNoScrollingClass() {
var name = '.no-scrolling',
rules = 'height: 100%; overflow: hidden;',
style = document.createElement('style');
style.type = 'text/css';
document.getElementsByTagName('head')[0].appendChild(style);

if (!style.sheet && !(<any>style.sheet).insertRule) {
((<any>style).styleSheet || style.sheet).addRule(name, rules);
} else {
(<any>style.sheet).insertRule(name + '{' + rules + '}', 0);
}
}

private createBackdropComponent(backdrop) {
backdrop.addClass('tour-backdrop').addClass('not-shown').css({
//display: 'none',
zIndex: this.TourConfig.get('backdropZIndex')
});
this.$body.append(backdrop);
}

private showBackdrop() {
this.viewWindow.top.removeClass('hidden');
this.viewWindow.bottom.removeClass('hidden');
this.viewWindow.left.removeClass('hidden');
this.viewWindow.right.removeClass('hidden');

setTimeout(() => {
this.viewWindow.top.removeClass('not-shown');
this.viewWindow.bottom.removeClass('not-shown');
this.viewWindow.left.removeClass('not-shown');
this.viewWindow.right.removeClass('not-shown');
}, 33);
}

private hideBackdrop() {
this.viewWindow.top.addClass('not-shown');
this.viewWindow.bottom.addClass('not-shown');
this.viewWindow.left.addClass('not-shown');
this.viewWindow.right.addClass('not-shown');
this.hideTarget();

setTimeout(() => {
this.viewWindow.top.addClass('hidden');
this.viewWindow.bottom.addClass('hidden');
this.viewWindow.left.addClass('hidden');
this.viewWindow.right.addClass('hidden');
}, 250);
}

createForElement(element: ng.IRootElementService, shouldPreventScrolling: boolean, isFixedElement: boolean, padding: IPadding) {
var position,
viewportPosition,
bodyPosition;

if (shouldPreventScrolling) {
this.preventScrolling();
}

position = this.$uibPosition.offset(element);
viewportPosition = this.$uibPosition.viewportOffset(element);
bodyPosition = this.$uibPosition.offset(this.$body);

if (isFixedElement) {
angular.extend(position, viewportPosition);
}

padding = this._processPadding(padding);

var pTop = Math.floor(position.top) - padding.top;
var pLeft = Math.floor(position.left) - padding.left;
var pHeight = Math.floor(position.height) + padding.top + padding.bottom;
var pWidth = Math.floor(position.width) + padding.left + padding.right;

var bTop = Math.floor(bodyPosition.top);
var bLeft = Math.floor(bodyPosition.left);
var bHeight = Math.floor(bodyPosition.height);
var bWidth = Math.floor(bodyPosition.width);

this.viewWindow.top.css({
position: isFixedElement ? 'fixed' : 'absolute',
top: 0,
left: 0,
width: '100%',
height: (pTop) + 'px'
});
this.viewWindow.bottom.css({
position: isFixedElement ? 'fixed' : 'absolute',
left: 0,
width: '100%',
height: (bTop + bHeight - pTop - pHeight) + 'px',
top: (pTop + pHeight) + 'px'
});
this.viewWindow.left.css({
position: isFixedElement ? 'fixed' : 'absolute',
left: 0,
top: pTop + 'px',
width: (pLeft) + 'px',
height: pHeight + 'px'
});
this.viewWindow.right.css({
position: isFixedElement ? 'fixed' : 'absolute',
top: pTop + 'px',
width: (bLeft + bWidth - pLeft - pWidth) + 'px',
height: pHeight + 'px',
left: (pLeft + pWidth) + 'px'
});
this.viewWindow.target.css({
position: isFixedElement ? 'fixed' : 'absolute',
top: pTop + 'px',
width: pWidth + 'px',
height: pHeight + 'px',
left: pLeft + 'px'
});

this.showBackdrop();

if (shouldPreventScrolling) {
this.preventScrolling();
}
}

hide() {
this.hideBackdrop();
this.hideTarget();
this.allowScrolling();
}

hideTarget(removeNotShow = true) {
this.viewWindow.target.addClass('not-shown');

if (!removeNotShow)
return;

setTimeout(() => {
this.viewWindow.target.addClass('hidden');
}, 250);
}

showTarget() {
this.viewWindow.target.removeClass('hidden');

setTimeout(() => {
this.viewWindow.target.removeClass('not-shown');
}, 33);
}

constructor(private TourConfig: ITourConfig, private $document: ng.IDocumentService, private $uibPosition: angular.ui.bootstrap.IPositionService, private $window: ng.IWindowService) {
var service = this;
var document = <HTMLDocument>(<any>$document[0])
this.$body = angular.element(document.body);
this.viewWindow = {
top: angular.element(document.createElement('div')),
bottom: angular.element(document.createElement('div')),
left: angular.element(document.createElement('div')),
right: angular.element(document.createElement('div')),
target: angular.element(document.createElement('div'))
}

this.createNoScrollingClass();

this.createBackdropComponent(this.viewWindow.top);
this.createBackdropComponent(this.viewWindow.bottom);
this.createBackdropComponent(this.viewWindow.left);
this.createBackdropComponent(this.viewWindow.right);
this.createBackdropComponent(this.viewWindow.target);

}

static factory(TourConfig: ITourConfig, $document: ng.IDocumentService, $uibPosition: angular.ui.bootstrap.IPositionService, $window: ng.IWindowService) {
return new TourBackdrop(TourConfig, $document, $uibPosition, $window);
}

_processPadding(padding: IPadding) {
if (!padding)
padding = { top: 0, left: 0, right: 0, bottom: 0 }

padding.top = padding.top || 0;
padding.left = padding.left || 0;
padding.right = padding.right || 0;
padding.bottom = padding.bottom || 0;

return padding;
}
}
}
57 changes: 57 additions & 0 deletions app/TypeScript/angular-ui-tour-config-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
module Tour {
export class TourConfigProvider {
config: ITourConfigProperties = {
placement: 'top',
animation: true,
popupDelay: 1,
closePopupDelay: 0,
enable: true,
appendToBody: false,
popupClass: '',
orphan: false,
backdrop: false,
backdropZIndex: 10000,
scrollOffset: 100,
scrollIntoView: true,
useUiRouter: false,
useHotkeys: false,

onStart: null,
onEnd: null,
onPause: null,
onResume: null,
onNext: null,
onPrev: null,
onShow: null,
onShown: null,
onHide: null,
onHidden: null
};

$get: [string, ($q: ng.IQService) => ITourConfig];

set(option, value) {
this.config[option] = value;
}
constructor() {
this.$get = ['$q', ($q) => {
angular.forEach(this.config, function (value, key) {
if (key.indexOf('on') === 0 && angular.isFunction(value)) {
this.config[key] = function () {
return $q.resolve(value());
};
}
});

return {
get: (option) => {
return this.config[option];
},
getAll: () => {
return angular.copy(this.config);
}
};
}];
}
}
}
570 changes: 570 additions & 0 deletions app/TypeScript/angular-ui-tour-controller.ts

Large diffs are not rendered by default.

151 changes: 151 additions & 0 deletions app/TypeScript/angular-ui-tour-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
module Tour {
export class TourHelper {
$state

constructor(private $templateCache: ng.ITemplateCacheService, private $http: ng.IHttpService, private $compile: ng.ICompileService, private $location: ng.ILocationService, private TourConfig: ITourConfig, private $q: ng.IQService, private $injector) {
if ($injector.has('$state')) {
this.$state = $injector.get('$state');
}
}

/**
* Helper function that calls scope.$apply if a digest is not currently in progress
* Borrowed from: https://coderwall.com/p/ngisma
*
* @param {$rootScope.Scope} scope
* @param {Function} fn
*/
safeApply(scope: ng.IScope, fn: () => any) {
var phase = scope.$$phase;
if (phase === '$apply' || phase === '$digest') {
if (fn && (typeof (fn) === 'function')) {
fn();
}
} else {
scope.$apply(fn);
}
}

/**
* Converts a stringified boolean to a JS boolean
*
* @param string
* @returns {*}
*/
stringToBoolean(string) {
if (string === 'true') {
return true;
} else if (string === 'false') {
return false;
}

return string;
}

/**
* This will attach the properties native to Angular UI Tooltips. If there is a tour-level value set
* for any of them, this passes that value along to the step
*
* @param {$rootScope.Scope} scope The tour step's scope
* @param {Attributes} attrs The tour step's Attributes
* @param {Object} step Represents the tour step object
* @param {Array} properties The list of Tooltip properties
*/
attachTourConfigProperties(scope, attrs, step, properties) {
angular.forEach(properties, (property) => {
if (!attrs[this.getAttrName(property)] && angular.isDefined(step.config(property))) {
attrs.$set(this.getAttrName(property), String(step.config(property)));
}
});
};

/**
* Helper function that attaches event handlers to options
*
* @param {$rootScope.Scope} scope
* @param {Attributes} attrs
* @param {Object} options represents the tour or step object
* @param {Array} events
* @param {boolean} prefix - used only by the tour directive
*/
attachEventHandlers(scope, attrs, options, events, prefix?) {

angular.forEach(events, (eventName) => {
var attrName = this.getAttrName(eventName, prefix);
if (attrs[attrName]) {
options[eventName] = () => {
return this.$q((resolve) => {
this.safeApply(scope, () => {
resolve(scope.$eval(attrs[attrName]));
});
});
};
}
});

};

/**
* Helper function that attaches observers to option attributes
*
* @param {Attributes} attrs
* @param {Object} options represents the tour or step object
* @param {Array} keys attribute names
* @param {boolean} prefix - used only by the tour directive
*/
attachInterpolatedValues(attrs, options, keys, prefix?) {

angular.forEach(keys, (key) => {
var attrName = this.getAttrName(key, prefix);
if (attrs[attrName]) {
options[key] = this.stringToBoolean(attrs[attrName]);
attrs.$observe(attrName, (newValue) => {
options[key] = this.stringToBoolean(newValue);
});
}
});

};

/**
* sets up a redirect when the next or previous step is in a different view
*
* @param step - the current step (not the next or prev one)
* @param ctrl - the tour controller
* @param direction - enum (onPrev, onNext)
* @param path - the url that the next step is on (will use $location.path())
* @param targetName - the ID of the next or previous step
*/
setRedirect(step, ctrl, direction, path, targetName) {
var oldHandler = step[direction];
step[direction] = (tour) => {
return this.$q((resolve) => {
if (oldHandler) {
oldHandler(tour);
}
ctrl.waitFor(targetName);
if (step.config('useUiRouter')) {
this.$state.transitionTo(path).then(resolve);
} else {
this.$location.path(path);
resolve();
}
});
};
};

/**
* Returns the attribute name for an option depending on the prefix
*
* @param {string} option - name of option
* @param {string} prefix - should only be used by tour directive and set to 'uiTour'
* @returns {string} potentially prefixed name of option, or just name of option
*/
getAttrName(option, prefix?) {
return (prefix || 'tourStep') + option.charAt(0).toUpperCase() + option.substr(1);
};
static factory($templateCache: ng.ITemplateCacheService, $http: ng.IHttpService, $compile: ng.ICompileService, $location: ng.ILocationService, TourConfig: ITourConfig, $q: ng.IQService, $injector: ng.IInjectStatic) {
return new TourHelper($templateCache, $http, $compile, $location, TourConfig, $q, $injector);
}
}
}
81 changes: 81 additions & 0 deletions app/TypeScript/angular-ui-tour-interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
module Tour {
export interface IPadding {
top: number;
left: number;
bottom: number;
right: number;
}

export interface ITourScope extends ng.IScope {
tour: TourController;
tourStep: ITourStep;

originScope: () => ITourScope;
isOpen: () => boolean;
}

export interface ITourStep {
nextPath?;
prevPath?;
backdrop?;
stepId?;
trustedContent?;
content?: string;
order?: number;
templateUrl?: string;
element?: ng.IRootElementService;
enabled?: boolean;
preventScrolling?: boolean;
fixed?: boolean;
isNext?: boolean;
isPrev?: boolean;
redirectNext?: boolean;
redirectPrev?: boolean;
nextStep?: ITourStep;
prevStep?: ITourStep;
show?: () => PromiseLike<any>;
hide?: () => PromiseLike<any>;
onNext?: () => PromiseLike<any>;
onPrev?: () => PromiseLike<any>;
onShow?: () => PromiseLike<any>;
onHide?: () => PromiseLike<any>;
onShown?: () => PromiseLike<any>;
onHidden?: () => PromiseLike<any>;
config?: (string) => any;
}

export interface ITourConfig {
get: (option: string) => any;
getAll: () => ITourConfigProperties;
}

export interface ITourConfigProperties {
name?: string;
placement: string;
animation: boolean;
popupDelay: number;
closePopupDelay: number;
enable: boolean;
appendToBody: boolean;
popupClass: string;
orphan: boolean;
backdrop: boolean;
backdropZIndex: number;
scrollOffset: number;
scrollIntoView: boolean;
useUiRouter: boolean;
useHotkeys: boolean;

onStart: (any?) => any;
onEnd: (any?) => any;
onPause: (any?) => any;
onResume: (any?) => any;
onNext: (any?) => any;
onPrev: (any?) => any;
onShow: (any?) => any;
onShown: (any?) => any;
onHide: (any?) => any;
onHidden: (any?) => any;

}
}
82 changes: 82 additions & 0 deletions app/TypeScript/angular-ui-tour-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@

module Tour {
export class uiTourService {
private tours: Array<TourController>

constructor(private $controller: ng.IControllerService) {
this.tours = [];
}

/**
* If there is only one tour, returns the tour
*/
getTour() {
return this.tours[0];
}

/**
* Look up a specific tour by name
*
* @param {string} name Name of tour
*/
getTourByName(name: string) {
return this.tours.filter((tour) => {
return tour.options.name === name;
})[0];
}

/**
* Finds the tour available to a specific element
*
* @param {jqLite | HTMLElement} element Element to use to look up tour
* @returns {*}
*/
getTourByElement(element) {
return angular.element(element).controller('uiTour');
};

/**
* Creates a tour that is not attached to a DOM element (experimental)
*
* @param {string} name Name of the tour (required)
* @param {{}=} config Options to override defaults
*/
createDetachedTour(name: string, config: ITourConfigProperties) {
if (!name) {
throw {
name: 'ParameterMissingError',
message: 'A unique tour name is required for creating a detached tour.'
};
}

config = config || <any>{};

config.name = name;
return (<any>this.$controller('uiTourController')).init(config);
};

/**
* Used by uiTourController to register a tour
*
* @protected
* @param tour
*/
_registerTour(tour) {
this.tours.push(tour);
};

/**
* Used by uiTourController to remove a destroyed tour from the registry
*
* @protected
* @param tour
*/
_unregisterTour(tour) {
this.tours.splice(this.tours.indexOf(tour), 1);
};

static factory($controller: ng.IControllerService) {
return new uiTourService($controller);
}
}
}
74 changes: 74 additions & 0 deletions app/TypeScript/angular-ui-tour-step-popup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
module Tour {
export class TourStepPopupDirective {
public restrict = 'EA';
public replace = true;
public scope = { title: '@', uibTitle: '@uibTitle', content: '@', placement: '@', animation: '&', isOpen: '&', originScope: '&' };
public templateUrl = 'tour-step-popup.html';
public link: (scope, element: ng.IRootElementService, attrs, ctrl: Tour.TourController) => void;

constructor(TourConfig: Tour.ITourConfig, smoothScroll, ezComponentHelpers) {
TourStepPopupDirective.prototype.link = function (scope, element: ng.IRootElementService, attrs, ctrl: Tour.TourController) {
var step = scope.originScope().tourStep,
ch = ezComponentHelpers.apply(null, arguments),
scrollOffset = step.config('scrollOffset');

//UI Bootstrap name changed in 1.3.0
if (!scope.title && scope.uibTitle) {
scope.title = scope.uibTitle;
}

//for arrow styles, unfortunately UI Bootstrap uses attributes for styling
attrs.$set('uib-popover-popup', 'uib-popover-popup');

element.css({
zIndex: TourConfig.get('backdropZIndex') + 2,
display: 'block'
});

element.addClass(step.config('popupClass'));

if (step.config('fixed')) {
element.css('position', 'fixed');
}

if (step.config('orphan')) {
ch.useStyles(
`:scope {
position: fixed;
top: 50% !important;
left: 50% !important;
margin: 0 !important;
-ms-transform: translateX(-50%) translateY(-50%);
-moz-transform: translateX(-50%) translateY(-50%);
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
.arrow
display: none;
}`
);
}

scope.$watch('isOpen', (isOpen: () => boolean) => {
if (isOpen() && !step.config('orphan') && step.config('scrollIntoView')) {
smoothScroll(element[0], {
offset: scrollOffset
});
}
});
};
}

public static Factory() {

var directive = (TourConfig: Tour.ITourConfig, smoothScroll, ezComponentHelpers) => {
return new TourStepPopupDirective(TourConfig, smoothScroll, ezComponentHelpers);
};

directive['$inject'] = ['TourConfig', 'smoothScroll', 'ezComponentHelpers'];

return directive;
}
}
}
226 changes: 226 additions & 0 deletions app/TypeScript/angular-ui-tour-step.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
module Tour {
export class TourStepCompiler {
private ctrl: TourController
private step: ITourStep
private events: Array<string>
private options: Array<string>
private tooltipAttrs: Array<string>
private orderWatch
private enabledWatch

public TourHelpers: TourHelper
public TourService: uiTourService
public $q: ng.IQService
public $sce: ng.ISCEService
public tourStepLinker

constructor(private scope: Tour.ITourScope, private element: ng.IRootElementService, private attrs: ng.IAttributes, private uiTourCtrl: Tour.TourController) {
this.initializeVariables();
if (attrs[this.TourHelpers.getAttrName('if')] !== undefined && attrs[this.TourHelpers.getAttrName('if')] === "false") {
return;
}

this.addWatches();
this.finalizeStep();
this.addStepToScope();
Object.defineProperties(this.step, {
element: {
value: element
}
});

//clean up when element is destroyed
scope.$on('$destroy', function () {
this.ctrl.removeStep(this.step);
this.orderWatch();
this.enabledWatch();
});
}

public static Factory() {

var compiler = (scope: Tour.ITourScope, element: ng.IRootElementService, attrs: ng.IAttributes, uiTourCtrl: Tour.TourController) => {
return new TourStepCompiler(scope, element, attrs, uiTourCtrl);
};

compiler['$inject'] = ['scope', 'element', 'attrs', 'uiTourCtrl'];

return compiler;
}

private initializeVariables() {
this.ctrl = this.getCtrl(this.attrs, this.uiTourCtrl);
this.step = {
show: () => {
this.element.triggerHandler('uiTourShow');
return this.$q((resolve) => {
this.element[0].dispatchEvent(new CustomEvent('uiTourShow'));
resolve();
});
},
hide: () => {
return this.$q((resolve) => {
this.element[0].dispatchEvent(new CustomEvent('uiTourHide'));
resolve();
});
},
stepId: (<any>this.attrs).tourStep,
enabled: true,
config: (option) => {
if (angular.isDefined(this.step[option])) {
return this.step[option];
}
return this.ctrl.config(option);
}
};
this.events = 'onShow onShown onHide onHidden onNext onPrev'.split(' ');
this.options = 'content title animation placement backdrop orphan popupDelay popupCloseDelay popupClass fixed preventScrolling scrollIntoView nextStep prevStep nextPath prevPath scrollOffset'.split(' ');
this.tooltipAttrs = 'animation appendToBody placement popupDelay popupCloseDelay'.split(' ');
}

private addWatches() {
this.TourHelpers.attachInterpolatedValues(this.attrs, this.step, this.options);
this.orderWatch = this.attrs.$observe(this.TourHelpers.getAttrName('order'), (order: number) => {
this.step.order = !isNaN(order * 1) ? order * 1 : 0;
this.ctrl.reorderStep(this.step);
});
this.enabledWatch = this.attrs.$observe(this.TourHelpers.getAttrName('enabled'), function (isEnabled) {
this.step.enabled = isEnabled !== 'false';
if (this.step.enabled) {
this.ctrl.addStep(this.step);
} else {
this.ctrl.removeStep(this.step);
}
});
}

private finalizeStep() {
//Attach event handlers
this.TourHelpers.attachEventHandlers(this.scope, this.attrs, this.step, this.events);

if (this.attrs[this.TourHelpers.getAttrName('templateUrl')]) {
this.step.templateUrl = this.scope.$eval(this.attrs[this.TourHelpers.getAttrName('templateUrl')]);
}

//If there is an options argument passed, just use that instead
if (this.attrs[this.TourHelpers.getAttrName('options')]) {
angular.extend(this.step, this.scope.$eval(this.attrs[this.TourHelpers.getAttrName('options')]));
}

//set up redirects
if (this.step.nextPath) {
this.step.redirectNext = true;
this.TourHelpers.setRedirect(this.step, this.ctrl, 'onNext', this.step.nextPath, this.step.nextStep);
}
if (this.step.prevPath) {
this.step.redirectPrev = true;
this.TourHelpers.setRedirect(this.step, this.ctrl, 'onPrev', this.step.prevPath, this.step.prevStep);
}

//for HTML content
this.step.trustedContent = this.$sce.trustAsHtml(this.step.content);
}

private addStepToScope() {

//Add step to tour
this.scope.tourStep = this.step;
this.scope.tour = this.scope.tour || this.ctrl;
if (this.ctrl.initialized) {
this.configureInheritedProperties();
this.ctrl.addStep(this.step);
} else {
this.ctrl.once('initialized', () => {
this.configureInheritedProperties();
this.ctrl.addStep(this.step);
});
}
}

private configureInheritedProperties() {
this.TourHelpers.attachTourConfigProperties(this.scope, this.attrs, this.step, this.tooltipAttrs/*, 'tourStep'*/);
this.tourStepLinker(this.scope, this.element, this.attrs);
}

private getCtrl(attrs, uiTourCtrl) {
var ctrl: Tour.TourController;

if (attrs[this.TourHelpers.getAttrName('belongsTo')]) {
ctrl = this.TourService.getTourByName(attrs[this.TourHelpers.getAttrName('belongsTo')]);
} else if (uiTourCtrl) {
ctrl = uiTourCtrl;
}

if (!ctrl) {
throw {
name: 'DependencyMissingError',
message: 'No tour provided for tour step.'
};
}

return ctrl;
}
}

export class TourStepDirective {
private tourStepDef;

public restrict: string;
public scope: boolean;
public require: string;
public compile: (element: ng.IAugmentedJQuery, attr: ng.IAttributes) => (...any) => TourStepCompiler;

constructor(private TourConfig: Tour.ITourConfig, private TourHelpers: Tour.TourHelper, private TourService: Tour.uiTourService, private $uibTooltip, private $q: ng.IQService, private $sce: ng.ISCEService) {
this.restrict = 'EA';
this.scope = true;
this.require = '?^uiTour';
this.tourStepDef = $uibTooltip('tourStep', 'tourStep', 'uiTourShow', {
popupDelay: 1 //needs to be non-zero for popping up after navigation
});

Tour.TourStepDirective.prototype.compile = (tElement, tAttrs) => {
TourStepCompiler.prototype.$q = $q;
TourStepCompiler.prototype.$sce = $sce;
TourStepCompiler.prototype.TourHelpers = TourHelpers;
TourStepCompiler.prototype.TourService = TourService;
TourStepCompiler.prototype.tourStepLinker = this.tourStepDef.compile(tElement, tAttrs);

if (!(<any>tAttrs).tourStep) {
tAttrs.$set('tourStep', '\'PH\''); //a placeholder so popup will show
}

return TourStepCompiler.Factory();
}
}

public static Factory() {

var directive = (TourConfig: Tour.ITourConfig, TourHelpers: Tour.TourHelper, TourService: Tour.uiTourService, $uibTooltip, $q: ng.IQService, $sce: ng.ISCEService) => {
return new TourStepDirective(TourConfig, TourHelpers, TourService, $uibTooltip, $q, $sce);
};

directive['$inject'] = ['TourConfig', 'TourHelpers', 'uiTourService', '$uibTooltip', '$q', '$sce'];

return directive;
}

private getCtrl(attrs, uiTourCtrl) {
var ctrl: Tour.TourController;

if (attrs[this.TourHelpers.getAttrName('belongsTo')]) {
ctrl = this.TourService.getTourByName(attrs[this.TourHelpers.getAttrName('belongsTo')]);
} else if (uiTourCtrl) {
ctrl = uiTourCtrl;
}

if (!ctrl) {
throw {
name: 'DependencyMissingError',
message: 'No tour provided for tour step.'
};
}

return ctrl;
}
}
}
144 changes: 144 additions & 0 deletions app/TypeScript/angular-ui-tour.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/// <reference path="../typings/jquery/jquery.d.ts" />
/// <reference path="../typings/angularjs/angular.d.ts" />
/// <reference path="../typings/angular-ui-bootstrap/angular-ui-bootstrap.d.ts" />
/* global Tour: false */

module Tour {
export class TourDirective {
public restrict = 'EA';
public scope = true;
public controller = 'uiTourController';
public link: (scope: Tour.ITourScope, element: ng.IRootElementService, attrs, ctrl: Tour.TourController) => void;

constructor(private TourHelpers: Tour.TourHelper) {
TourDirective.prototype.link = (scope: Tour.ITourScope, element: ng.IRootElementService, attrs, ctrl: Tour.TourController) => {
//Pass static options through or use defaults
var tour = {
name: attrs.uiTour,
templateUrl: null,
onReady: null
}

this.interpolateValues(scope, attrs, tour);
this.finalizeTour(scope, attrs, tour);
this.finalizeScope(scope, tour, ctrl);
};
}

public static Factory() {

var directive = (TourHelpers: Tour.TourHelper) => {
return new TourDirective(TourHelpers);
};

directive['$inject'] = ['TourHelpers'];

return directive;
}

private interpolateValues(scope, attrs, tour) {
var events = 'onReady onStart onEnd onShow onShown onHide onHidden onNext onPrev onPause onResume'.split(' '),
properties = 'placement animation popupDelay closePopupDelay enable appendToBody popupClass orphan backdrop scrollOffset scrollIntoView useUiRouter useHotkeys'.split(' ');

//Pass interpolated values through
this.TourHelpers.attachInterpolatedValues(attrs, tour, properties, 'uiTour');

//Attach event handlers
this.TourHelpers.attachEventHandlers(scope, attrs, tour, events, 'uiTour');

}

private finalizeTour(scope, attrs, tour) {
//override the template url
if (attrs[this.TourHelpers.getAttrName('templateUrl', 'uiTour')]) {
tour.templateUrl = scope.$eval(attrs[this.TourHelpers.getAttrName('templateUrl', 'uiTour')]);
}

//If there is an options argument passed, just use that instead
if (attrs[this.TourHelpers.getAttrName('options')]) {
angular.extend(tour, scope.$eval(attrs[this.TourHelpers.getAttrName('options')]));
}
}

private finalizeScope(scope, tour, ctrl) {
//Initialize tour
scope.tour = ctrl.init(tour);
if (typeof tour.onReady === 'function') {
tour.onReady();
}

scope.$on('$destroy', function () {
ctrl.destroy();
});
}
}
}

((app: ng.IModule) => {
'use strict';

app.config(['$uibTooltipProvider', ($uibTooltipProvider: angular.ui.bootstrap.ITooltipProvider) => {
$uibTooltipProvider.setTriggers({
'uiTourShow': 'uiTourHide'
});
}]);

})(angular.module('bm.uiTour', ['ngSanitize', 'ui.bootstrap', 'smoothScroll', 'ezNg', 'cfp.hotkeys']));

(function (app: ng.IModule) {
'use strict';

app.factory('uiTourBackdrop', ['TourConfig', '$document', '$uibPosition', '$window', Tour.TourBackdrop.factory])
.factory('TourHelpers', ['$templateCache', '$http', '$compile', '$location', 'TourConfig', '$q', '$injector', Tour.TourHelper.factory])
.factory('uiTourService', ['$controller', Tour.uiTourService.factory])
.provider('TourConfig', [Tour.TourConfigProvider])
.controller('uiTourController', ['$timeout', '$q', '$filter', 'TourConfig', 'uiTourBackdrop', 'uiTourService', 'ezEventEmitter', 'hotkeys', Tour.TourController])
.directive('uiTour', ['TourHelpers', Tour.TourDirective.Factory()])
.directive('tourStepPopup', ['TourConfig', 'smoothScroll', 'ezComponentHelpers', Tour.TourStepPopupDirective.Factory()])
.directive('tourStep', ['TourConfig', 'TourHelpers', 'uiTourService', '$uibTooltip', '$q', '$sce', Tour.TourStepDirective.Factory()])
.run(['$templateCache', function ($templateCache) {
$templateCache.put("tour-step-popup.html",
`<div class="popover tour-step"
tooltip-animation-class="fade"
uib-tooltip-classes
ng-class="{ in: isOpen() }">
<div class="arrow"></div>
<div class="popover-inner tour-step-inner">
<h3 class="popover-title tour-step-title" ng-bind="title" ng-if="title"></h3>
<div class="popover-content tour-step-content"
uib-tooltip-template-transclude="originScope().tourStep.config('templateUrl') || 'tour-step-template.html'"
tooltip-template-transclude-scope="originScope()"></div>
</div>
</div>
`);
$templateCache.put("tour-step-template.html",
`<div>
<div class="popover-content tour-step-content" ng-bind-html="tourStep.trustedContent"></div>
<div class="popover-navigation tour-step-navigation">
<div class="btn-group">
<button class="btn btn-sm btn-default" ng-if="tourStep.isPrev" ng-click="tour.prev()">&laquo; Prev</button>
<button class="btn btn-sm btn-default" ng-if="tourStep.isNext" ng-click="tour.next()">Next &raquo;</button>
<button class="btn btn-sm btn-default" data-role="pause-resume" data-pause-text="Pause"
data-resume-text="Resume" ng-click="tour.pause()">Pause
</button>
</div>
<button class="btn btn-sm btn-default" data-role="end" ng-click="tour.end()">End tour</button>
</div>
</div>
`);
}]);
} (angular.module('bm.uiTour')));

(function (window) {
function CustomEvent(event, params) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent('CustomEvent');
evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
return evt;
}

CustomEvent.prototype = window.Event.prototype;

window.CustomEvent = CustomEvent;
})(window);