Necesito evitar que el usuario acceda a una determinada página simplemente escribiendo url y presionando enter. El usuario puede acceder a esta página siguiendo algún asistente, por ejemplo. Sé que javaScript puede monitorear los eventos de clic en el navegador, ¿es posible monitorear los cambios de url? En otras palabras, ¿es posible saber cómo llegó el usuario a esa página? (¿agregó la ruta por sí mismo o, naturalmente, simplemente siguiendo el asistente de la aplicación?)

Editar:
Caso de uso: cuestionario en línea con varias preguntas y páginas y, finalmente, una página de resultados. Quiero evitar que el usuario acceda a la página de resultados después de responder solo una pregunta.

0
Alex 11 dic. 2016 a las 20:53
¿Alguna razón por la que el usuario no debería utilizar el enlace directo?
 – 
Icepickle
11 dic. 2016 a las 20:56
1
No puede confiar en nada del cliente. Ni siquiera puede estar seguro de que el cliente sea un navegador web. Sin embargo, puede insistir en parámetros particulares con la solicitud HTTP.
 – 
Pointy
11 dic. 2016 a las 20:56
Sí, digamos que hay un cuestionario en línea con una página de resultados. Quiero evitar que el usuario acceda a la página de resultados después de responder solo una pregunta.
 – 
Alex
11 dic. 2016 a las 20:57
1
Si su JavaScript se está ejecutando en la página nueva y en la página anterior, entonces sí: verifique la ubicación frente a una ubicación almacenada previamente. Cada página cargará un nuevo programa JavaScript y esto no es exclusivo del estado de almacenamiento.
 – 
user2864740
11 dic. 2016 a las 20:57
2
I want to prevent from the user getting to the results page after answering only one question. No veo por qué eso debería resolverse con JavaScript del lado del cliente. Seguramente, si alguien responde cualquier cantidad de preguntas, debe enviar sus opciones. Si fueran a la página de resultados, no esperaría que sucediera nada. A lo sumo, se mostraría algún tipo de mensaje, pero sin resultados.
 – 
VLAZ
11 dic. 2016 a las 21:04

1 respuesta

La mejor respuesta

Como su pregunta realmente no tiene un código de inicio, es difícil saber con qué marco está trabajando actualmente. Debido a esto, implementé un cuestionario muy básico (realmente no hace la parte de respuesta, no muestra ningún resultado y, en general, es bastante básico, pero quería proporcionar un ejemplo algo completo).

Para detectar los cambios dentro de su página (la ruta hash actual), puede suscribirse al evento hashchange de la ventana, por ejemplo:

window.addEventListener('hashchange', handleRoute.bind(router));

Sin embargo, para asegurarse de que el usuario no navegue directamente a la ruta específica, también puede suscribirse al evento load.

window.addEventListener('load', handleRoute.bind(router));

Para validar (en mi caso) si el usuario puede estar en la ruta actual, agregué un método de validación, que simplemente verifica que:

  • En caso de una pregunta: la pregunta actual es la siguiente pregunta a responder
  • En caso de los resultados: asegúrese de haber recibido todas las respuestas

Esto se maneja en esta parte:

function isPageValid(route, qry) {
  switch (route.scope) {
    case 'questions':
      // only allow the question page when the current question is the actual one in the querystring
      return (parseInt(qry.split('=')[1]) === answered.length) && answered.length <= questions.length;
      // only allow the results when we have all the answers
    case 'results':
      return answered.length === parseInt(questions.length);
  }
  // everything else should be valid (eg: default page)
  return true;
}

Como la ruta actual se carga de todos modos en función de mi enrutador, no tengo que preocuparme de que otras páginas sean válidas en ese momento, por lo que si no son preguntas ni resultados, siempre envío true de vuelta: )

En caso de que no esté allí, se mostrará la página predeterminada

Por lo demás, el violín es bastante básico, te muestra la página de inicio, 2 preguntas potenciales y realmente no puedes seleccionar nada, pero el principio de mostrar solo la página de resultados cuando se completó la lista de preguntas, debería ser claro.

Agregué algunos comentarios al código, con la esperanza de que todo esté claro :)

'use strict';
var questions = [{
    title: 'Is SO a good resource',
    answers: ["yes", "no", "sometimes"],
    correct: 2
  }, {
    title: 'Do you like to use stackoverflow on a daily basis',
    answers: ["yes", "no"],
    correct: 0
  }],
  answered = [];

// bind the nextQuestion element to the nextQuestion function
document.getElementById('nextQuestion').addEventListener('click', nextQuestion);
/*
 * @method nextQuestion
 * here the user will click when he wants to navigate to the next question
 * If all questions are completed, it will navigate to the results page
 * If there are more questions, it will navigate to /q=currentQuestion+1
 */
function nextQuestion(e) {
  answered.push({
    value: 0
  });
  if (answered.length < questions.length) {
    document.location.href = '#/q=' + answered.length;
  } else {
    document.location.href = '#/results';
  }
  e.preventDefault();
}

/*
 * @method showQuestion
 * Gets called when the question gets changed
 */
function showQuestion(route, qry) {
  var currentQuestion = answered.length;
  document.getElementById('currentQuestion').innerHTML = currentQuestion;
  document.getElementById('question').innerHTML = questions[currentQuestion].title;
  if (currentQuestion === questions.length - 1) {
    document.getElementById('nextQuestion').innerHTML = 'show results';
  } else {
    document.getElementById('nextQuestion').innerHTML = 'next question';
  }
}

/*
 * @method showResults
 * Dummy method, answered are should contain all current answers
 * just prints a message to the console
 */
function showResults(route, qry) {
  console.log('can finally see the results :)');
}

/* 
 * @method isPageValid
 * Validates the current route & qry
 * @param route the current active route
 * @param qry the current querystring that was validated to get to this route
 * @returns true if the page is valid, false if it is not valid
 */
function isPageValid(route, qry) {
  switch (route.scope) {
    case 'questions':
      // only allow the question page when the current question is the actual one in the querystring
      return (parseInt(qry.split('=')[1]) === answered.length) && answered.length <= questions.length;
      // only allow the results when we have all the answers
    case 'results':
      return answered.length === parseInt(questions.length);
  }
  // everything else should be valid (eg: default page)
  return true;
}

/*
 * @method handleRoute
 * All routes are part of it's context (this object)
 * Loops all properties of the context and checks which route matches the current part after #
 */
function handleRoute() {
  var qry = document.location.href.split('#')[1],
    result, defaultRoute, prop;
  // hide all .scoped-block elements
  document.querySelectorAll('.scoped-block').forEach(item => {
    item.classList.add('hidden');
  });
  console.log('current query: ' + qry);
  for (prop in this) {
    if (this.hasOwnProperty(prop)) {
      if (!this[prop].test) {
        defaultRoute = this[prop];
      } else {
        if (this[prop].test.test(qry)) {
          result = this[prop];
          console.log('matches: ' + prop);
        }
      }
    }
  }
  // if we have a designated page, validate it
  if (result && !result.validate(result, qry)) {
    // the page is not allowed to be shown (route to the default page)
    console.info('cannot navigate to ' + result.scope + ' page (yet)')
    result = undefined;
  }
  // make sure result contains the current valid route or the defaultRoute
  result = result || defaultRoute;
  console.log('current scope: ' + result.scope);

  // set the current scope as the visible element
  document.getElementById(result.scope).classList.remove('hidden');
  if (result.action) {
    result.action(result, qry);
  }
}

// setup the available routes + potential validations
var router = {
  questionPage: {
    test: /\/q=[0-9]{1,2}$/,
    validate: isPageValid,
    action: showQuestion,
    scope: 'questions'
  },
  resultPage: {
    test: /\/results$/,
    validate: isPageValid,
    action: showResults,
    scope: 'results'

  },
  startPage: {
    action: function() {
      // reset the answers
      answered.splice(0, answered.length);
    },
    scope: 'startPage'
  }
};

// adds the handle route method to the onload + hashchange method
window.addEventListener('hashchange', handleRoute.bind(router));
window.addEventListener('load', handleRoute.bind(router));
.hidden { display: none; visibility: hidden; }
.scoped-block {
  // dummy element
}
<div id="questions" class="hidden scoped-block">
  <h1>Question <span id="currentQuestion"></span></h1>
  <span id="question"><select id="optSelect"></select></span>
  <div>
    <a href="#">Restart quiz</a> - 
    <a href="#" id="nextQuestion">Next question</a>
  </div>
</div>
<div id="results" class="hidden scoped-block">
  <h1>results</h1>
  <a href="#/">Restart quiz</a>
</div>
<div id="startPage" class="scoped-block">
  <h1>Welcome to a small quiz</h1>
  <p>
  When you completed the quiz, you will be send to <a href="#/results">this</a> results page, which shouldn't be accessible before hand.
  </p>
  <p>
  To start the quiz, click <a href="#/q=0">here</a>
  </p>
</div>
2
Icepickle 11 dic. 2016 a las 22:33
Gracias por la respuesta detallada.
 – 
Alex
14 dic. 2016 a las 14:36