linous
linous

Reputation: 263

Vue.js Image Map Disable area after click

I have an image with 3 areas. When I click on each area, I want a series of questions to appear. I've done this, but I want to change it a bit. Since I don't want to redirect it to a page, I am giving # as href link and I'm getting the id of the area based by event.currentTarget.id Then I have three v-ifs with a condition for each component.

This is the jfiddle: https://jsfiddle.net/rau4apyg/

<div id="app">

<img id="Image-Maps-Com-image-maps-2017-03-16-100553" src="http://www.image-maps.com/m/private/0/fdfutap3klci37abfmuasc2mk7_screenshot.png" border="0" width="588" height="414" orgWidth="588" orgHeight="414" usemap="#image-maps-2017-03-16-100553" alt="" />
<map name="image-maps-2017-03-16-100553" id="ImageMapsCom-image-maps-2017-03-16-100553">
<area shape="rect" coords="586,412,588,414" alt="Image Map" style="outline:none;" title="Image Map" href="http://www.image-maps.com/index.php?aff=mapped_users_0" />
<area  id="component1" alt="" title="comp1" href="#" shape="poly" coords="420,228,296,34,180,226,178,228" style="outline:none;" target="_self" v-on:click="compId($event)"    />
<area  id="component2" alt="" title="comp2" href="#" shape="poly" coords="92,368,176,234,292,234,294,368,298,236,290,368" style="outline:none;" target="_self" v-on:click="compId($event)"     />
<area  id="component3" alt="" title="comp3" href="#" shape="poly" coords="506,366,296,366,296,232,422,232" style="outline:none;" target="_self" v-on:click="compId($event)"    />
</map> 
<h1> Title here </h1>
  <div v-if="compid === 'component1'"> 
  component1 is clicked, questions are shown 
  </div>
    <!-- show questions in for loop -->

    <div v-if="compid === 'component2'">
      2 is clicked
    </div>
    <div v-if="compid === 'component3'">
      3 is clicked
    </div>
    <div v-show="questionIndex === quiz.questions.length -1"> 
    <button v-on:click="addAnswers">
        submit
      </button> 
    <h2>
    Quiz finished, plase continue with Component 2 questions. 
  </h2>
  <button v-on:click="goToNextComponent">
    Next
  </button> 
</div>
new Vue({
  el: '#app',
  data: {
    quiz: {
      questions: [],
      answers: []
    },
    // Store current question index
    questionIndex: 0,
    total:0,
    show: true,
    compid: '',
    flag: false
  },
  mounted: {
  //functions here
  },
  computed: {
    //functions here
  },
  // The view will trigger these methods on click
  methods: {
    //some functions

    //return id of clicked component
    compId: function(event){
      this.compid = event.currentTarget.id;
      console.log(event.currentTarget.id); // returns the name of the id clicked.
    } ,
    addAnswers: function(){
        //store answers in Firebase    
    //vm.$forceUpdate();
    flag = true; //change the flag
    console.log(flag);
    },
    goToNextComponent: function(){
    }
}
});

I want to complete the questions in order, that means: First do questions of Component1, click submit to save the answers, then show questions of Component2, answer them and then move to Component3.

If a user is done with Component1, I want him to not be able to answer these questions again, disable it in some way, and go to component 2. When he completes the next component, I want to disable that too and go to the last one.

I don't know how to make it work this way. I had two thoughts: 1) When I click the Submit button, I change a flag to true. So I know component 1 is clicked and I add it to the v-if clause. I tried adding it by using an && operator but it didn't work. 2) Have a next button after submit (I am not sure if it sounds ok) and when that is clicked, show the next questions that are included in component 2.

P.S.My database is on Firebase and I have all the questions in an array. e.g. First 10 questions are of component1, next 8 of component 2, etc. Maybe would it be better to add a field to seperate them? Right now it's like this:

{
  "questions" : [ {
    "q_options" : [ "Yes", "No", "Don't know" ],
    "q_text" : "Do you agree with blah blah?"
  }}

Maybe I could add a component_option: 1 Any ways you would suggest to solve these problems?

Upvotes: 2

Views: 2691

Answers (1)

Bert
Bert

Reputation: 82459

I've modified your approach a little bit. Essentially you were on the right track; you just need to keep track of which questions are completed. Then, when someone clicks on a particular image map, check to see if that has already been completed and, if so, prevent navigating to it.

const quiz = new Vue({
  el: '#app',
  data: {
    quiz: {
      questions:  [
        {
          "q_options" : [ "Yes", "No", "Don't know" ],
          "q_text" : "Do you agree with blah blah?",
          coords:"420,228,296,34,180,226,178,228",
          shape:"poly",
          completed: false,
          answer: null
        },
        {
          "q_options" : [ "Yes", "No", "Don't know" ],
          "q_text" : "Question Number 2?",
          coords:"92,368,176,234,292,234,294,368,298,236,290,368",
          shape: "poly",
          completed: false,
          answer: null
        },
        {
          "q_options" : [ "Yes", "No", "Don't know" ],
          "q_text" : "Question Number 3?",
          coords:"506,366,296,366,296,232,422,232",
          shape:"poly",
          completed: false,
          answer: null
        }],
      answers: []
    },
    currentQuestion: null,
    quizCompleted: false
  },
  methods: {
    selectQuestion(question){
      if (!question.completed)
        this.currentQuestion = question;
      else
        alert("This question has already been completed!")
    },
    completeQuestion(){
      this.currentQuestion.completed = true;
      let currentIndex = this.quiz.questions.indexOf(this.currentQuestion); 
      if ( currentIndex === this.quiz.questions.length - 1){
          this.quizCompleted = true;
          this.currentQuestion = null;     
          this.quiz.answers = this.quiz.questions.map(q => q.answer)
      } else {
        this.currentQuestion = this.quiz.questions[++currentIndex];
      }
    }
  },
  mounted(){
    this.currentQuestion = this.quiz.questions[0]
  }
});

And the template:

<div id="app">
  <img id="Image-Maps-Com-image-maps-2017-03-16-100553" src="http://www.image-maps.com/m/private/0/fdfutap3klci37abfmuasc2mk7_screenshot.png" border="0" width="588" height="414" orgWidth="588" orgHeight="414" usemap="#image-maps-2017-03-16-100553" alt="" />
  <map name="image-maps-2017-03-16-100553" id="ImageMapsCom-image-maps-2017-03-16-100553">
    <area v-for="question in quiz.questions" 
          :shape="question.shape" 
          :coords="question.coords"
          @click="selectQuestion(question)"/>
  </map> 
  <div v-if="currentQuestion">
    <h1> {{currentQuestion.q_text}} </h1>
    <template v-for="(option, index) in currentQuestion.q_options">
      <input type="radio" :value="option" v-model="currentQuestion.answer">
      <label>{{option}}</label> 
      <br />
    </template>
    <button @click="completeQuestion">Complete</button>

  </div>
  <div v-if="quizCompleted">
    <h1>You're Done!</h1>
    {{quiz.answers}}
  </div>
</div>

Here is a working example.

Some key points.

  • Your map areas appear to be directly tied to questions, so I just made them data. Then you can iterate over them to make your image map. This wil make it easier when you want to reuse the Vue for a different quiz.
  • You don't really want to be showing and hiding what you were calling "components". Just build out one that represents the current question and change it's data. Generally in Vue, you want to drive your interface through data.

This is not polished, but it accomplishes your main goal; using the map for navigation, and preventing navigating to completed questions.

Upvotes: 2

Related Questions