Skip to content
This repository has been archived by the owner on Feb 17, 2021. It is now read-only.

Support onBeforeShow callback for content loaded via Ajax #56

Open
stanleywym opened this issue Feb 24, 2014 · 10 comments
Open

Support onBeforeShow callback for content loaded via Ajax #56

stanleywym opened this issue Feb 24, 2014 · 10 comments

Comments

@stanleywym
Copy link

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?

@timlindvall
Copy link
Collaborator

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 call hopscotch.startTour() and it might pick up where it left off. This might cause Hopscotch to fire any onStart callbacks again, though.

I hope this helps!

@timlindvall
Copy link
Collaborator

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.

@luksurious
Copy link

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.

@luksurious
Copy link

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());
        }
    }]
};

@gfrobenius
Copy link

I need this enhancement as well. In the meantime, thanks for the workaround! It works great.

@kamranayub
Copy link

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).

@vasvir
Copy link

vasvir commented Jun 11, 2015

@kamranayub +1

Vassilis

@ataft
Copy link

ataft commented Mar 4, 2016

+1
I would love onBefore and onAfter events for all actions, next, back, etc.

@n0nag0n
Copy link

n0nag0n commented May 5, 2016

Thank you @luksurious Your workaround saved me.

@halfhp
Copy link

halfhp commented Nov 24, 2017

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"
        }
    ]
};

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants