Use same url but load different pages/screens

Hello,
I am building a survey with meteor in the style of https://www.typeform.com/templates/t/self-evaluation/
that means that I have just one url where my survey will be routed

e.g. /experiment

and after each step(clicking buttons , answering to questions) a new page (meaning just one new question) will be loaded but within same url (/experiment) and not new or load the template in the end of the same page.
How can I manage to do this without creating multiple pages (html and js) and append them each time to different urls within FlowRouter but just use one? Also I just tried using separate templates html files within the main template/html file but still it just adds new template in the bottom of the page and not loading a new page
thank you in advance

You could use {{>Template.dynamic template=stepTemplate data=stepData}} in the template for /experiment', wherestepTemplate` is a ReactiveVar that will contain the template names for the different steps. Here is an extremely simplified example how this could work (just edited a bit to make all parts play better)

Template.experiment.onCreated(function () {
  const instance = this;
  instance.step = new ReactiveVar('stepOne');
  instance.steps = {
    stepOne: {
      nextStep: 'stepTwo',
      title: 'Next Step',
      message: '<h2>This is the 1st step</h2>',
    },
    stepTwo: {
      nextStep: 'stepThree',
      title: 'One more Step',
      message: '<h2>This is the 2nd step</h2>',
    },
    stepThree: {
      message: '<h2>You did it, you finished all steps</h2>',
    },
  };
});
Template.experiment.helpers({
  stepTemplate() {
    return Template.instance().step.get();
  },
  stepData() {
    const instance = Template.instance();
    var step = instance.steps[instance.step.get()];
    // return the data the step template should receive
    return step;
  },
});
Template.experiment.events({
  'click button'(event, instance) {
    var nextStep = event.currentTarget.dataset.nextStep;
    instance.step.set(nextStep);
  },
});

And the templates could be

<template name="experiment">
 {{>Template.dynamic template=stepTemplate data=stepData}}
</template>
<template name="stepOne">
 <h1>Step One</h1>
  {{{message}}}
 {{#if nextStep}} <button data-next-step="{{nextStep}}">{{title}}</button>{{/if}}
</template>
<template name="stepTwo">
 <h1>Step Two</h1>
 {{{message}}}
 {{#if nextStep}} <button data-next-step="{{nextStep}}">{{title}}</button>{{/if}}
</template>
<template name="stepThree">
 <h1>Step Three</h1>
  {{{message}}}
 {{#if nextStep}} <button data-next-step="{{nextStep}}">{{title}}</button>{{/if}}
</template>
4 Likes

Thank you so much for your instant help this is what I need to do :slight_smile:
I run your example and I m working on my project the way you suggested!

May I ask one more question, inside each step (page) I have radio buttons, dropbox lists which are filled one at a step by the user, what is the best way to catch the user input per template in an object and end up in an object containing all the user input given lets say three steps/pages?
I tried doing it within the

Template.experiment.events({
  'click button'(event, instance) {
    var nextStep = event.currentTarget.dataset.nextStep;
    instance.step.set(nextStep);
 options = {
            "question_1": $('input[name="question_1"]:checked').val(),
            "question_2": $('#question_2').val(),
  },
});

but is seems as it gets empty each time a new template is loaded so best way to do it is i believe use new reactive instances and create
Template.stepOne.events {()}
Template.stepTwo.events {()}

where I collect the feedback, am I right?

You’ll just need to pass whatever you choose to the next template. I used the explicit data context

<template name="experiment">
  {{>Template.dynamic template=stepTemplate data=stepData}}
</template>

where I have a helper stepData that provides the required data for each step. Each step could save stuff into a global variable that gets passed to the next template

@jamgold Thanks once more!
Can you please provide an example?
What I am doing currently is that inside the click button’(event, instance) event, where each time a button is selected to continue to next step/template, I have created a variable options where I am saving input from each template by checking each time in which stepTemplate the step is. I get what I need but is there any other more convenient way I did not catch?

  Template.maas_experiment.events({
        'click button'(event, instance) {
          var nextStep = event.currentTarget.dataset.nextStep;
          instance.step.set(nextStep);
          stepTemplate=Template.instance().step.get()


          if (stepTemplate=='stepThree' ){

            options = {
              "question_1": $('input[name="question_1"]:checked').val(),
              "question_2": $('#question_2').val(),
}
}
if (stepTemplate=='stepFour' ){

            console.log('we are here stepFour options', options)

            var question_3=parseInt($('input[name="question_3"]:checked').val());
            var question_4=parseInt($('input[name="question_4"]:checked').val());

          options.question_3=question_3
          options.question_4=question_4

}``

There are so many ways this could be done. I experimented a bit more with this and found the following to be a more generic solution. I’m sure someone could come up with a better one, however

Template.experiment.onCreated(function () {
  const instance = this;
  instance.step = new ReactiveVar('stepOne');
  instance.steps = {
    // place to collect form data from different steps
    formData:{
    },
    stepOne: {
      nextStep: 'stepTwo',
      title: 'Next Step',
      message: '<h2>This is the 1st step</h2>',
      waitForAction: new ReactiveVar(true),
      fruitRadioLabel: 'most',
      fruitRadioName: 'mostFruit',
    },
    stepTwo: {
      nextStep: 'stepThree',
      title: 'One more Step',
      message: '<h2>This is the 2nd step</h2>',
      waitForAction: new ReactiveVar(true),
      fruitRadioLabel: 'second',
      fruitRadioName: 'secondFruit',
    },
    stepThree: {
      message: '<h2>You did it, you finished all steps</h2>',
      // startOver: 'stepOne',
    },
  };
});

In the event handler for my step button I check for form data. I created a my own template that renders the Next button and pass in parent information so it is available in instance.data

Template.templateWithForm.events({
  'change input'(event, instance) {
    var step = instance.data.step.get();
    var currentStep = instance.data.steps[step];
    var nextStep = instance.data.steps[currentStep.nextStep];
    var formData = instance.data.steps.formData;
    if ('waitForAction' in currentStep) currentStep.waitForAction.set(false);
    formData[event.currentTarget.name] = event.currentTarget.value;
  },
});

Hi again @jamgold,

thank you for the previous answer, it was not that clear to me though and I continue to follow my approach and using events on each step to capture the input data. I was wondering though how can I show up an alert at each step when not all the fields are filled in and keep the current template displayed and not moving on to the next page/template?
I am trying using this but does not work

 'click #NextPart': async function (event, instance){
          event.preventDefault();
          options = {
            "x_value": parseInt($('input[name="x_value"]:checked').val()),
            "y_value": parseInt($('#y_value').val()),
            "b_value": parseInt($('input[name="b_value"]:checked').val()),
    
            };

          Session.set('options',options)

          console.log('current stepTemplate  ',stepTemplate)
          answers=Session.get('options')
          console.log('answers',answers)
          if (answers!==null) {
            Object.keys(answers).forEach(function (key) {
              console.log('options loop key',answers[key])
              if ((answers[key]==4) || isNaN(answers[key])){
                console.log('problem here fill in all!!')
                swal({
                  title:"Please fill all the required fields!"
                });
                stepTemplate='stepTwo'///set the current template to the same to prevent moving to the next one
                console.log('check again stepTemplate',stepTemplate)
            

              }
            });
          }

        }

Instead of a Session I would simply store the values in the instance. Where is stepTemplate coming from? That should be something reactive. Your code doesn’t seem to write the changed stepTemplate back to where it could be interpreted by blaze to perform any kind of action (ReactiveVar).

Hi,
I apologize for all these questions but I am not that familiar with meteor.
regarding the instance I assume is the same advice you have given me in previous post, but did not get it. I will provide an example of how I currently do it (the wrong way) and hopefully you can tell me how I can fix it and keep these values in instance instead of Session.
The html of the dynamic template ‘stepTwo’ looks similar to this:

<template name="stepTwo">
<div class="form-check-inline">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="x_value" id="x_value" value="1">Yes
</label>
 </div>
<div class="form-check-inline">
<label class="form-check-label">
<input type="radio" class="form-check-input" name="x_value" id="x_value" value="2">No
</label>
</div>
<div class="form-group">
<select class="form-control" name="y_value" id="y_value" >
<option value="4"></option>
<option value="0">Never</option>
<option value="1">Once/few times per month</option>
<option value="2">Once/few times per week</option>
<option value="3">Every day</option>                
</select>
</div>
</template>

and each dynamic template has a lot of radio, check buttons which I need to keep every time they are filled in, and before leave the template to move on to the next by selecting the button nextstep , a check that all the fields were filled in should be done

Template.experiment.onCreated(function () {
  const instance = this;
  instance.step = new ReactiveVar('stepOne');
  instance.steps = {
    stepOne: {
....
    },
    stepTwo: {
      nextStep: 'stepThree',
      title: 'One more Step',
      message: '<h2>This is the 2nd step</h2>',
      x_value: parseInt($('input[name="x_value"]:checked').val()),
      y_value: parseInt($('#y_value').val()),

    },
    stepThree: {
...    },
  };
});

Regarding the stepTemplate, I think that I need to do something like that

instance.step = new ReactiveVar('stepTwo');

to force the template to stay in stepTwo template in the case that not all the values are filled in

you would need to instance.step.set('stepTwo') to keep it on that step, not create a new reactive variable.

Saving the form values is standard Javascript and not Meteor specific.

I encountered a problem only in Firefox though when loading last template (which is the longest page)the page is by default scrolled down, so the user should always scroll up when reaches this template/page/view
I have tried

      window.scrollTo(0,0);

or

      $(window).scrollTop(0)

but with no result…Any ideas?

Where are you calling this?
You need to make sure it runs last after all rendering is done, so it doesn’t get changed by a re-render

Maybe attach an onRendered callback to the last step?

Template.lastStep.onRendered(function(){
  windows.scrollTo(0,0)
});

well after some more testing I put it inside the events of the overall template, since in the onclick of the button of each page/nested template(stepOne, stepTwo etc) the next page is loaded

Template.experiment.events({
    'click .btn-lg': async function (event, instance) {
      event.preventDefault();
      window.scrollTo(0,0);

the only thing is that at times I observe some quick scrolling up before it changes…

I tried also to apply this
window.scrollTo(0,0);
on the last step onrendered but with no result

  Template.lastStep.onRendered(function(){
    windows.scrollTo(0,0)
})

I suppose this is because the majority of the functionality of all pages is gathered within the overall template in which all the steps/pages are included.