easelinc / tourist

Simple, flexible tours for your app
http://easelinc.github.io/tourist/
1.23k stars 100 forks source link

TypeError: Cannot read property 'getBoundingClientRect' of undefined when used with AngularJS routes #25

Open michaelkleinhenz opened 10 years ago

michaelkleinhenz commented 10 years ago

When tourist.js is used with AngularJS routes, selecting an element as target fails if the target is contained in an external route resource that is loaded on demand.

Example step definition:

{ content: "Instructions", highlightTarget: true, nextButton: true, target: $("#someid"), my: "top center", at: "bottom center" }

When the tour is started from the AngularJS framework page and $("#someid") is not loaded yet (it is loaded when the route is selected, causing Angular to GET the html from the server and dynamically inserting it into the DOM), starting the step fails at the following lines in tourist.js:

BootstrapTip.prototype._getTargetBounds = function(target) { var el, size; el = target[0]; if (typeof el.getBoundingClientRect === 'function') { ...

el is undefined. I suppose this is caused by evaluating $("#someid") when the tour is created rather than when the step is created/shown. I added a delay in the previous step to make sure $("#someid") is defined when the step is created:

(in teardown() in previous step:) setTimeout(function() { tour.next(); }, 2000);

The behaviour is that the tour pauses, the browser displays the new screen (loading the route fragment from server, inserting it into the DOM), 2 seconds later, the error is shown in the console:

Uncaught TypeError: Cannot read property 'getBoundingClientRect' of undefined tourist.js:559 Tourist.Tip.BootstrapTip.BootstrapTip._getTargetBounds tourist.js:559 Tourist.Tip.BootstrapTip.BootstrapTip._caculateTargetPosition tourist.js:477 Tourist.Tip.BootstrapTip.BootstrapTip._setPosition tourist.js:460 Tourist.Tip.BootstrapTip.BootstrapTip.setTarget tourist.js:389 Tourist.Tip.Bootstrap.Bootstrap.setTarget tourist.js:290 Tourist.Tip.Base.Base.render tourist.js:78 Tourist.Tour.Tour.onChangeCurrentStep tourist.js:939 (anonymous function) tourist.js:5 triggerEvents backbone.js:209 Backbone.Events.trigger backbone.js:148 .extend.set backbone.js:357 Tourist.Tour.Tour._showStep tourist.js:999 Tourist.Tour.Tour.next tourist.js:921 (anonymous function) tourist.js:5 (anonymous function) tours.js:84

michaelkleinhenz commented 10 years ago

After further tests, it seems that tourist.js can not use elements as step targets that are not contained in the DOM when the tour is created. Seems that dynamically added DOM elements are invisible to steps.

michaelkleinhenz commented 10 years ago

Found a workaround: closely reading the doc, it contains the solution. Return a new target selector value from the setup() function:

setup: function(tour, options) { return { target: $('#dynamicElement') }; }

As I think this is a standard usecase, I would suggest that "target" can also take a string representing a selector. If the value is a string, the selector is evaluated during step init:

{ content: "Instructions", target: "#someid", my: "top center", at: "bottom center" }

aajhiggs commented 9 years ago

@michaelkleinhenz did this work for you? I came to the same conclusion about the use of the setup() function, but when I try this (Backbone.js) the issue seems to persist.

michaelkleinhenz commented 9 years ago

Yes, still works, but with the tourist version from back in summer 2014. I didn't try it with a newer version.