js/controllers/VoteViewController.js
import ViewController from '~/controllers/ViewController';
import ErrorManager from '~/helpers/ErrorManager';
import Theme from '~/models/Theme';
import Auth from '~/models/Auth';
import AuthModalTemplate from '~/template/login/AuthModalTemplate';
import ModalController from '~/controllers/ModalController';
import LoadingIcon from '~/svg/LoadingIcon';
export const VOTE_ACTIVE_CLASS = 'action-button--selected';
export const VOTE_DISABLED_CLASS = 'action-button--disabled';
export const VoteFailed = Symbol('Vote.Error.RequestFailed');
export const VoteUnauthorized = Symbol('Vote.Error.VoteUnauthorized');
export const VoteInvalid = Symbol('Vote.Error.VoteInvalid');
/**
* Controls voting
* @abstract
*/
export default class VoteViewController extends ViewController {
/**
* @param {HTMLElement} voteButton
* @property {string} voteType
*/
constructor(voteButton, voteType) {
super();
voteButton.controller = this;
this._voteType = voteType;
this._voteTotal = voteButton.getElementsByClassName('vote-count')[0];
this._voteIcon = voteButton.getElementsByTagName('svg')[0];
this._loadingIcon = LoadingIcon.cloneNode(true);
this._iconParent = this._voteIcon.parentNode;
this._isLoading = false;
this._root = voteButton;
this._isActive = voteButton.classList.contains(VOTE_ACTIVE_CLASS);
// Ignore if not a valid vote button
if (voteButton.classList.contains(VOTE_DISABLED_CLASS)) return this;
voteButton.addEventListener("click", ::this.toggleState);
}
/**
* Sets the loading state
* @param {boolean} state
*/
setLoading(state) {
// Check if desired state matches existing (to ensure always transition)
if (state === this._isLoading) return;
if (state) {
this._iconParent.replaceChild(this._loadingIcon, this._voteIcon);
} else {
this._iconParent.replaceChild(this._voteIcon, this._loadingIcon);
}
this._isLoading = state;
}
/**
* Sets the vote total
* @param {number} value - total number amount of votes
*/
setVoteTotal(value) {
while (this._voteTotal.lastChild) {
this._voteTotal.removeChild(this._voteTotal.lastChild);
}
this._voteTotal.appendChild(document.createTextNode(value));
}
/**
* Returns a vote request for the data
* @param {string} voteType
* @param {boolean} status
* @return {Request}
* @abstract
*/
getRequest(voteType, status) { return null; }
/**
* What to set vote too.
* @param {boolean} status
*/
async setVote(status) {
let voteRequest = this.getRequest(this._voteType, status);
try {
this.setLoading(true);
// Check if logged in at all
let auth = Auth.shared;
if (auth.isAuthorized) {
let response = await voteRequest.run();
this.setVoteTotal(response.total);
this.setVoteActivity(response.voted);
} else {
ModalController.shared.present(AuthModalTemplate.shared);
}
} catch(error) {
switch (error.response && error.response.status) {
case 401:
ErrorManager.raise(`You must be authorized to vote.`, VoteUnauthorized);
break;
case 403:
ErrorManager.raise(`You cannot vote on your own post.`, VoteInvalid);
case 500:
ErrorManager.raise(`Server error during vote.`, VoteFailed);
break;
default:
ErrorManager.silent(error, `Unexpected error while voting.`);
}
} finally {
this.setLoading(false);
}
}
/**
* Sets vote activity
* @param {boolean} activity - vote activity
*/
setVoteActivity(activity) {
this._isActive = activity;
if (activity) {
this._root.classList.add(VOTE_ACTIVE_CLASS);
} else {
this._root.classList.remove(VOTE_ACTIVE_CLASS);
}
}
/**
* Toggles vote controller value.
*/
toggleState() {
// Don't toggle if loading
if (this._isLoading) return;
this.setVote(!this._isActive).catch(::ErrorManager.report);
}
}