jswny
jswny

Reputation: 167

Allow Button Click Only Once in Meteor

In my Meteor project, I have two buttons. One button is an upvote button, which adds a point to the score of an entry, while the other is a downvote button, which does the opposite. My site doesn't require login.

How can I restrict it so that any given device can only click either the upvote or downvote button initially, and then if that device decides to change it's vote, it should be able to click only the other button, and so on?

Upvotes: 2

Views: 206

Answers (2)

Kishor
Kishor

Reputation: 2677

You can use HTML 5 LocalStorage. But it will only work on latest browsers. If you want suppoert for old browsers as well then you might be interested in this question as well. If your user base doesn't use very old browsers then you can do it with LocalStorage like this,

In template's created callback,

Template.yourTemplate.created = function () {
    var template = this;
    var userVote = null;
    if(typeof(Storage) !== "undefined") {
        userVote = localStorage.getItem("userVote");
    }
    template.userVote = new ReactiveVar(userVote); //or you can use Session.setDefault("userVote", userVote)
}

When user clicks on the up or down button

Template.yourTemplate.events({
    'click #upButton': function (ev, template) {
         localStorage.setItem("userVote", "up");
         template.userVote.set("up"); // or Session.set("userVote", "up");
    },
    'click #downButton': function (ev, template) {
         localStorage.setItem("userVote", "down");
         template.userVote.set("down"); // or Session.set("userVote", "down");
    }
});

Then to disable buttons, you can do something like this in your helpers,

Template.yourTemplate.helpers({
    'isUpButtonDisabled': function () {
         var template = Template.instance();
         var userVote = template.userVote.get(); // or Session.get("userVote");
         return userVote === "up";
    },
    'isDownButtonDisabled': function (ev, template) {
         var template = Template.instance();
         var userVote = template.userVote.get(); // or Session.get("userVote");
         return userVote === "down";
    }
});

Update: This answer uses localStorage so that the application can keep track of the user's vote even when user visits the same site at a later date, which was what OP was trying to do, since user can vote without a login.

EDIT: Based on your comment to have different votes for different templates/topics. Assuming you have current topic's id in template's current data. You can do something like this,

In template's created callback,

Template.yourTemplate.created = function () {
    var template = this;
    template.userVote = new ReactiveVar(null); //or you can use Session.setDefault("userVote", null)
    template.autorun(function () {
        var data = Template.currentData();
        var topicId = data.topicId;
        var userVote = null;
        if(typeof(Storage) !== "undefined") {
            userVote = localStorage.getItem("userVote" + topicId);
        }
        template.userVote.set(userVote); //or you can use Session.set("userVote", userVote);
    });
}

When user clicks on the up or down button

Template.yourTemplate.events({
    'click #upButton': function (ev, template) {
         var topicId = this.topicId;
         localStorage.setItem("userVote" + topicId, "up");
         template.userVote.set("up"); // or Session.set("userVote", "up");
    },
    'click #downButton': function (ev, template) {
         var topicId = this.topicId;
         localStorage.setItem("userVote" + topicId, "down");
         template.userVote.set("down"); // or Session.set("userVote", "down");
    }
});

Then to disable buttons, you can do something like this in your helpers,

Template.yourTemplate.helpers({
    'isUpButtonDisabled': function () {
         var template = Template.instance();
         var userVote = template.userVote.get(); // or Session.get("userVote");
         return userVote === "up";
    },
    'isDownButtonDisabled': function (ev, template) {
         var template = Template.instance();
         var userVote = template.userVote.get(); // or Session.get("userVote");
         return userVote === "down";
    }
});

Upvotes: 2

zer00ne
zer00ne

Reputation: 44098

It sounds like a regular old radio button should do the trick.

I made some fancier stuff as well, see this CodePen.

Update

Added @4castle's rescind vote function. Nice touch.

Update 2

Per OP's request, the radio buttons are now the arrows.

CodePen 2

html,
body {
  box-sizing: border-box;
  background: #111;
  color: #DDD;
  font: 400 16px/1.4'Verdana';
  height: 100vh;
  width: 100vw;
}
*,
*:before,
*:after {
  box-sizing: inherit;
  margin: 0;
  padding: 0;
  border: 0 none hlsa(0%, 0, 0, 0);
  outline: 0 none hlsa(0%, 0, 0, 0);
}
fieldset {
  margin: 0 1em 1em 1em;
  padding: 8px;
  border-radius: 9px;
  border: 3px double #FF8;
  width: 100%;
  max-width: 19em;
}
legend {
  font: small-caps 700 1.5rem/2"Palatino Linotype";
  color: #FD1;
}
/* RadZ */

#radz input.chkrad {
  display: none;
}
#radz input.chkrad + label {
  color: #EEE;
  background: transparent;
  font-size: 16px;
}
#radz input.chkrad:checked + label {
  color: #0ff;
  background: transparent;
  font-size: 16px;
}
#radz input.chkrad + label span {
  display: inline-block;
  width: 18px;
  height: 18px;
  margin: -1px 15px 0 0;
  vertical-align: baseline;
  cursor: pointer;
}
#radz input + label span {
  background: transparent;
  line-height: 100%;
}
input.A + label span:before {
  content: '△';
  color: #0ff;
  font-style: normal;
  font-weight: 700;
  font-size: 24px;
}
input.A:checked + label span:before {
  padding: -5px 15px 5px;
  font-size: 16px;
}
input.A:checked + label span:before {
  content: '▲';
  color: #0ff;
  font-style: normal;
  font-weight: 700;
  font-size: 24px;
}
input.B + label span:before {
  content: '▽';
  color: #0ff;
  font-style: normal;
  font-weight: 700;
  font-size: 24px;
}
input.B:checked + label span {
  padding: -5px 15px 5px;
  font-size: 16px;
}
input.B:checked + label span:before {
  content: '▼';
  color: #0ff;
  font-style: normal;
  font-weight: 700;
  font-size: 24px;
}
input.fade + label span,
input.fade:checked + label span {
  -webkit-transition: background 0.7s linear;
  -moz-transition: background 0.7s linear;
  transition: background 0.7s linear;
}
<fieldset id="radz" name="radz">
  <legend>Vote</legend>
  <input type='radio' name='rad' id="rad1" class="chkrad A fade" value='1' />
  <label for="rad1"><span></span>Up</label>
  <br/>
  <input type='radio' name='rad' id="rad2" class="chkrad B fade" value='2' />
  <label for="rad2"><span></span>Down</label>
  <br/>
</fieldset>

// Rescind vote function provided by 4castle
// http://stackoverflow.com/users/5743988/4castle

var selectedRad;
var voteRads = document.querySelectorAll('input[name="vote"]');
for (var i = 0; i < voteRads.length; i++) {
  voteRads[i].onclick = function() {
    if (selectedRad == this) {
      this.checked = false;
      selectedRad = null;
    } else {
      selectedRad = this;
    }
  };
}
.rad1 + label:after {
  content: '△';
}
.rad1:checked + label:after {
  content: '▲';
}
.rad2 + label:after {
  content: '▽';
}
.rad2:checked + label:after {
  content: '▼';
}
<input id="up" type="radio" class="rad1" name="vote">
<label for="up"></label>
<br/>
<label>Vote</label>
<br/>
<input id="down" type="radio" class="rad2" name="vote">
<label for="down"></label>

Upvotes: 2

Related Questions