hopscotch
hopscotch copied to clipboard
Support onBeforeShow callback for content loaded via Ajax
I have a scenario where a form will be loaded via Ajax upon clicking on a button. I want to have a step to target on the first input field of the form. I've tried to load the form in onNext callback of the previous step. But it seems like the position of the next step's element is calculated before onNext is triggered and the next step is being skipped because the form has yet to be loaded at that time. Is it possible to add onBeforeShow callback to allow loading of content via Ajax?
Hmmm... if I'm reading the Hopscotch code correctly, the onNext callback should get invoked before moving on to showing the next step (line 1622 calls the callback, then line 1648 tells the bubble to render itself for the next step). However, there doesn't appear to be any mechanism in place to tell Hopscotch to hold up for asynchronous behaviors, so HopscotchBubble.render()
is likely to get invoked before the AJAX call is resolved.
It appears an ongoing theme with the open issues is the necessity to reveal via API a method to tell Hopscotch to recalculate its positioning after the DOM is changed. This could be coupled with a step config option that tells Hopscotch not to render the bubble until it's told to do so (thus your callback would be responsible to call Hopscotch.rerender()
once the AJAX call resolves).
In the interim, there's a few (somewhat hacky) options I can think of that might help...
- Use
step.delay
to delay the showing of the next step, which will in turn delay the calculation of where the bubble should render. Only problem, though, is that if the AJAX response takes longer than the delay, you'll still end up with an error. - Use an element on the page that you know to exist before the AJAX call (maybe the container you're placing your AJAX response content into?).
- Dunno how well this will work... but try setting the
multipage
flag on your previous step, then once the content is loaded callhopscotch.startTour()
and it might pick up where it left off. This might cause Hopscotch to fire anyonStart
callbacks again, though.
I hope this helps!
Oh crumbs... sorry... the previous comment is inaccurate. Hopscotch doesn't reach line 1622 until after it's determined what the next step should be by querying the step targets. So, given that, only options two and three are still viable, but option two is only viable if the target exists on the page before triggering onNext.
IMO, I don't think this is desired behavior... the docs aren't specific, but I think the general impression is that onNext would trigger first, as an immediate action to clicking the Next button, instead of waiting until we've determined what the next step should be. But, perhaps the original intent is that we shouldn't do anything that might affect the page (things that onNext might carry out) until we've actually determined where we're going next.
I think I'm going to play around with option three (the multipage flag) a bit... if this works as a viable option (after addressing the above), this might be a reasonable path forward for adding this sort of "waiting" functionality to Hopscotch. multipage
and waitForAsync
could both end up proxying to the same logic internally and hopscotch.rerender()
would itself call hopscotch.startTour()
or do some other action that reboots the tour from its old state.
Any thoughts? I'm curious to know what everyone's general impressions are around the onNext callback and whether what's described above is in sync with everyone else's use case, or if the "suppress until we've found something relevant" behavior is desired.
I was trying to use hopscotch in a similar scenario, where content is loaded via AJAX, and the tour should target an element from that loaded content.
While the multipage
and starting the tour after the elements are visible works, it is not so nice.
Would be great if an async
option would be available.
As a reference, I am using this structure to work around this limitation:
var waitForElementVisible = function(element, callback) {
var checkExist = setInterval(function() {
$element = typeof element == "function" ? $(element()) : $(element);
if ($element.is(':visible')) {
clearInterval(checkExist);
if (typeof callback == "function") {
callback();
}
}
}, 100);
};
var nextOnCallback = function(callback) {
var currentStep = window.hopscotch.getCurrStepNum();
callback(function() {
window.hopscotch.startTour(theTour, currentStep);
});
};
var theTour = {
id: theId,
steps: [{
title: 'title',
content: 'content',
target: 'target',
placement: 'bottom',
onNext: function() {
$('.ajax-call')[0].click();
nextOnCallback(function(callback) {
waitForElementVisible(function() {
return $('.element-to-wait-for');
}, callback);
});
},
multipage: true
}, {
title: 'title',
content: 'content',
target: '.element-to-wait-for',
placement: 'bottom',
onPrev: function() {
window.hopscotch.startTour(theTour, window.hopscotch.getCurrStepNum());
}
}]
};
I need this enhancement as well. In the meantime, thanks for the workaround! It works great.
Yah I'd definitely rather use support for deferred/async to properly await showing.
I would prefer a full lifecycle mechanism, like:
{
onBeforeShow: function () {
// void
},
waitOnBeforeShow: function () {
// return a deferred object here
},
onAfterShow: function () {
// void
},
canGoNext: function () {
return true; // return bool
},
canGoPrev: function () {
return false; // return bool
}
}
This would solve almost all issues regarding waiting for async stuff, doing things before/after showing, and preventing/canceling next/prev actions (even returning true/false for onNext
and onPrev
to cancel/allow would work).
@kamranayub +1
Vassilis
+1 I would love onBefore and onAfter events for all actions, next, back, etc.
Thank you @luksurious Your workaround saved me.
I was able to get the desired effect by returning false
from my onNext()
callback, after it kicks off the async code to complete before proceeding to step2. (This could be an Ajax request, or whatever)
I then resume the tutorial on step 2 at the end of my async call, since returning false
from onNext()
ends the tour.
Something like this: (sorry if its messy - i dont work with JS that often)
var myTour = {
id: "tour",
steps: [
{
title: "Step1",
content: "This is step1.",
target: "something",
multipage: true,
onNext: function () {
doSomethingAsyncWithOnCompleteCb(function() {
// do some stuff
...
// show step2 as soon as the async work concludes:
hopscotch.startTour(myTour, 1); // step[1] == "Step2"
})
return false;
}
},
{
title: "Step2",
content: "Select a repo from the search results.",
target: "somethingElse"
}
]
};