Hopscotch tour step on dynamically generated content

I am implementing several of Hopscotch tours in my app.

So far I have managed to make many tours without any problem but today, I am facing a challenge I could not resolve far.

My question is: how do I get a tour step target to work on dynamically generated content?

Here is the HTML:

<div class="pacote-destino-quartos-wrapper">
    <div class="pacote-destino-quartos-internal-wrapper">
        <h4>Todos os Destinos</h4>
        <div class="dynamic_nested_form_wrapper quartos_external_wrapper" data-destino="todos">
            <span class="add-child-link-wrapper">
                <a href="javascript:void(0)" class="add_child btn btn-info" data-association="quartos">
                    <i class="icon-plus icon-white"></i>
                </a>
            </span>
        </div>
    </div>
</div>

Whenever I click the link it dynamically creates a div which contains many elements; one of them is a div with a class called .quarto-config-wrapper.

If I try to make my Hopscotch tour go to this element, it does not work; my guess the dynamically created elements are not available in the DOM for manipulation.

Here is my Hopscotch steps code:

{
    title: "Adicionar um novo quarto",
    content: "content here",
    target: $('.add-child-link-wrapper')[0],
    placement: "left",
    width: 500,
    yOffset: -15,
    nextOnTargetClick: true,
    showNextButton: false
},
{
    title: "Menu de configuração do quarto",
    content: "content here",
    target: $('.quarto-config-wrapper')[0],
    placement: "left",
    width: 700,
    yOffset: -15,
    nextOnTargetClick: true,
    showNextButton: false,
    delay: 1200
}

The first step works but the second does not.

What am I doing wrong and how can I fix it?

I searched all over the place for a solution to this issue and this post is the one that came the closest but not quite definitive solution, so here it goes:

{ // This is the previous step
    element: "#idElement",
    title: "The Title",
    content: "The Content",
    onNext: function(tour) {
            tour.end();
            var checkExist = setInterval(function() {
                // This is the element from the next step. 
                $element = $('#idElementFromNextStep'); 

                if ($element.is(':visible')) {
                    clearInterval(checkExist);
                    tour.start(true); // True is to force the tour to start
                    tour.goTo(1); // The number is your next index (remember its base 0)
                }
            }, 100);
    },
    multipage: true, // Required
    orphan: true // Recommended
},{ // This is the step that was not working
    element: "#idElementFromNextStep",
    title: "Title of the step",
    content: "Details of the step",
    orphan: true,
}

So what this does is basically stop the tour when the next is fired, wait for the element to be added to the DOM and then restart the tour from the correct step by its index.

I borrowed a bit of code from @Luksurious. His solution kind of works (not for Bootstrap Tour though) but in any case it generates a flick when it loads the next step and goes back to the correct one.

I strongly advise against using a delay, it might seem to work on your local environment but its extremely dangerous to speculate with how long it takes a client to load.

Hope it helps someone else!

Hopscotch tour step on dynamically generated content, I searched all over the place for a solution to this issue and this post is the one that came the closest but not quite definitive solution, so here it goes: { // This is  2 Hopscotch tour step on dynamically generated content Jul 16 '14 2 poedit and xml files Nov 3 '10 2 Disable Doctrine Timestampable auto-updating the `updatedAt` field on certain update Jul 22 '16

One hackaround I'm using is the delay field on each step. That is:

{
  title: "+Log",
  content: "Lipsum",
  target: "#log_activity_nav",
  placement: "left",
  nextOnTargetClick: true


},

{

  title: "Phone",
  content: "The bottom section is where each individual log and detail lives, sorted by importance.",
  target: "#activity_log_activity_log_brokers_attributes_0_contact_attributes_phone", // this target is part of an element that is dynamically generated
  delay: 2000, // wait 2 seconds for this element to load
  placement: "top"
},

This seems to fill my needs for now, although obviously it's not guaranteed to work depending on what you're loading.

If anyone has a more elegant solution I'm all ears!

Use dynamic target, I searched all over the place for a solution to this problem, and this post was the closest, but not entirely final solution, so here it is: { // This is the previous step  Hopscotch supports internationalization via i18n object in tour config. The default language is English. If you need a tour in a language other than English, pass in translated text in your tour config. Text used for previous, next, skip, done and close buttons, as well as, tour step numbers can be translated. A Hopscotch tour consists of a

What I do to wait for AJAX calls is the following:

{
  title: "Title",
  content: "Content",
  target: '.triggerAjax',
  placement: 'bottom',
  onNext: function() {
    $('.triggerAjax').click();

    var checkExist = setInterval(function() {
      $element = $('.dynamicallyLoaded');

      if ($element.is(':visible')) {
        clearInterval(checkExist);

        window.hopscotch.startTour(window.hopscotch.getCurrTour(), window.hopscotch.getCurrStepNum());
      }
    }, 100);
  },
  multipage: true
},

Using multipage tells hopscotch to not continue. Then the onNext function checks if the element we want to target next is present and starts the tour again.

Of course, if you need this more often, extract it as a general function to keep your code clean and short.

hopscotch in dyanamic created elements · Issue #203 · LinkedInAttic , I'm trying to set the target dynamically because I don't have the final id of the element. var tour = { id: "AdminChangePasswordAndUsername", steps: [ content: "Clique no link enviar credenciais e, de seguida, confirme o envio das is needed in order to successfully target the dynamically created divs  12 – Hopscotch. Demo Download. Hopscotch is a framework to make it easy for developers to add product tours to their pages. Hopscotch accepts a tour JSON object as input and provides an API for the developer to control rendering the tour display and managing the tour progress. 13 – pageguide.js. Demo Download

Another solution (which works really well if the modal you are showing is another page) is to borrow from what the multi-page tour example. Essentially, on the page popping up the modal is where you would start the tour. However, on the page the modal actually loads (or as part of the dynamically generated content), you could do something like if (hopscotch.getState() == 'intro_tour:1') { hopscotch.startTour() }, which would essentially continue the tour started by the loader page. YMMV with this method, depending on what your modal loads.

Hopscotch, Hi, I have been using hopscotch to create tour for my site. Im stuck in a step that i have to step on an element that is created on previous step. target: "div[viewid​='taskpane']", title: "Select Widget", content: "Click the dropdown  The TourDataProvider is a standalone class building non persisted objects dynamically, adjusted for each step of the tour (because different charts require different data to actually show something interesting). All "destructive" actions are forbidden in tour mode, with things such as:

I couldn't get anyone's suggestions to work, the problem seems to be that the target attribute for the dynamically generated content is calculated at the moment the tour starts (when it is not yet on the page).

What works for me is to nest one tour inside another. Here's my tested and working code:

var tourP1 = {
  id: "tutp1",
  steps: [
    {
      title: "Step 1",
      content: "Lorem ipsum dolor.",
      target: "header",
      placement: "bottom"
    },
    {
      title: "Step 2",
      content: "Lorem ipsum dolor.",
      target: document.querySelector('#content'),
      placement: "top",
      multipage: true,
      onNext: function() {
        // Trigger dynamic content
        $('.someElement').trigger('click');
        // Wait for your target to become visible
        var checkExist = setInterval(function() {
          $element = $('.someElement').find('.myElement');
          if ($element.is(':visible')) {
            clearInterval(checkExist);
            // End this tour
            hopscotch.endTour(true);
            // Define the next tour
            var tourP2 = {
              id: "tutp2",
              steps: [
                {
                  title: "Step 3",
                  content: "Lorem ipsum dolor.",
                  target: $('.someElement').find('.myElement')[0],
                  placement: "bottom"
                }
              ]
            }
            // Start the nested tour
            hopscotch.startTour(tourP2);
          }
        }, 100);
      }
    },
    // This is needed for the onNext function in the previous step to work
    {
      title: "",
      target: "",
      placement: ""
    }
  ]
};

One caveat here is that the step number returns to 1 when the nested tour is started. A hack would be to pad the tour with some placeholder steps and start from the 'real' first step (yuck!). What we really need is a way to specify the step number in the step options.

javascript, A Hopscotch tour consists of a tour id, an array of tour steps defined as JSON It is highly recommended to never show user-generated content in a Hopscotch tour. This may be because you are dynamically creating a tour on the server. Now, you should have the first step of the tour as shown in the following image. Step 4 – Designing Hidden Steps Once the first step is completed, we need to show the next set of steps, which

Hopscotch Highlight example - JSFiddle, if try make hopscotch tour go element, not work. , guess because elements dynamically created not in dom. here hopscotch steps code: Node content is presented as HTML code in a “traditional” Webbrowser. However, because the framework leaves much freedom as to the actual implementation of nodes and aspects, the HTML code can be stored as a single static file, it can be assembled from multiple XML or HTML fragments, or it can be generated dynamically at runtime.

(online interactive) framework with multilingual support – Hopscotch!, <p>A Hopscotch tour consists of a tour id, an array of tour steps defined as JSON It is highly recommended to never show user-generated content in a Hopscotch tour. This may be because you are dynamically creating a tour on the server. It is too easy to use generated code which is not up to date. This can be a hard to detect cause of code consistency problems if the generated code is used across multiple projects. If using code review as part of merge requests, etc., changes in the generated code can swamp the interesting changes made by the developer.

Smooth and Lightweight jQuery Site Feature Tours Plugin, A Hopscotch tour consists of a tour id, an array of tour steps defined as It is highly recommended to never show user-generated content in a Hopscotch tour. This may be because you are dynamically creating a tour on the  Discuss how the following content types can add value to a website: text, images, animation, audio and video, multimedia, and dynamically generated content. Value-added content is information that is relevant, informative, and timely; accurate and high of quality; and usable.

Comments
  • I have tried to call from the first static element up from the dinamically created... did not work
  • The Delay solution has worked for me as well... but it is definetely a "hackaround" as you said!
  • I think the this passed in to startTour should be window.hopscotch.getCurrTour().
  • I up-voted this answer as it was what I was looking for but it's not quite working for me even after my above change to the line that starts the tour. I can't seem to start the second step by calling startTour from within the onNext function. I can if I get a reference to the tour object outside of it and then pass that in tostartTour.
  • My tours are inside a bigger structure where the current tour is saved in a variable. What you could do is save the tour object in a variable onStart: function () { window.lastTour = window.hopscotch.getCurrTour(); } and then access that variable from within the interval function.