[ad_1]
Decentralization / Social media
Implementing voting
In today’s instalment, we’ll be adding the ability to register a vote for or against a particular debate. We start from where we left off last time:
git clone https://github.com/jeremyorme/debate.git
cd debate
git checkout release/0.0.2
Before we get on to that though, a couple of small cosmetic improvements…
To give a bit more space, we’ll extend the cards out to the edge of the screen and just leave a small one pixel gap between them by adding a new file src/App.css
containing:
ion-card {
margin: 0 0 1px 0;
}
And importing this into src/App.tsx
:
import './App.css';
Then we’ll wrap the message input in src/Pages/MessagePage.tsx
with an IonCard
and set the IonButton
property size="small"
to fix its background color and layout:
<IonCard>
<IonGrid>
<IonRow>
<IonCol>
<IonInput placeholder="What do you think?" value={description} onIonChange={e => updateDescription(e.detail.value)} />
</IonCol>
<IonCol size="auto">
<IonButton size="small" fill="clear" disabled={description.length == 0} onClick={() => addMessage()}>
<IonIcon icon={arrowForwardSharp} />
</IonButton>
</IonCol>
</IonRow>
</IonGrid>
</IonCard>
Storing votes
Now let’s move on to the main topic and update our AppData
class to store votes either for or against a debate.
We’re going to do something a bit different this time and instead of an append-only collection, we’ll allow votes to be changed by the person that owns them. Let’s add the following code to AppData
:
// Votesprivate _votes: CollectionManager<IVote> = new CollectionManager<IVote>();
async loadVotes(debateId: string) {
if (!this._db)
return;
const collectionName = 'debate-' + debateId + '-votes';
this._votes.init(await this._db.collection(collectionName, {
publicAccess: AccessRights.ReadAnyWriteOwn,
conflictResolution: ConflictResolution.LastWriteWins
}));
}
Note that the collection name includes debateId
so each debate gets its own votes collection.
We use ReadAnyWriteOwn
public access with LastWriteWins
conflict resolution. That means that people can only write to their own key (_id = publicKey
) but they can overwrite it with a new value as often as they like.
We add the usual accessors:
votes(): IVote[] {
return this._votes.entries() || [];
}onVotes(callback: () => void) {
return this._votes.onUpdated(callback);
}
addVote(message: IVote) {
if (this._publicKey)
this._votes.addEntry({ ...message, _id: this._publicKey });
}
In addition, we want to get our own voting direction so that we can show in the UI whether we have already voted up or down on a particular debate:
ownVoteDirection(): VoteDirection {
if (!this._publicKey)
return VoteDirection.Undecided;
return this._votes.entry(this._publicKey)?.direction || VoteDirection.Undecided;
}
We need our public key to retrieve our own entry so we pass it into the init
method and store it as a member of AppData
:
private _publicKey: string | null = null;async init(db: IDb, publicKey: string) {
this._db = db;
this._publicKey = publicKey;
await this._loadDebates();
}
We therefore need to update loadDb
in src/App.tsx
to get the public key from dbClient
and pass it to init
:
const publicKey = dbClient.publicKey();
if (!publicKey)
return;appData.init(db, publicKey);
That’s the data sorted, now we need to update the UI…
Voting in the UI
We’ll add an icon button to the messages page that shows the vote status and allows a vote to be cast by clicking it. This is shown in the top right corner of the screenshot at the top of this article.
First let’s load the votes for the debate by adding calls to appData.loadVotes
into the first useEffect
in src/pages/MessagesPage.tsx
:
useEffect(() => {
appData.loadMessages(id, side);
appData.loadVotes(id);
return appData.onDebatesUpdated(() => {
setDebateTitle(appData.debateTitle(id));
appData.loadMessages(id, side);
appData.loadVotes(id);
});
}, []);
Then we’ll need another effect to update the latest value of our own vote direction:
useEffect(() => {
return appData.onVotes(() => {
setOwnVoteDirection(appData.ownVoteDirection());
});
}, []);
We need to add a state variable to access and update this:
const [ownVoteDirection, setOwnVoteDirection] = useState(appData.ownVoteDirection());
Now we can add the icon button to the toolbar:
<IonButtons slot="end">
{side == 'against' ? <IonButton slot="icon-only" onClick={() => updateOwnVoteDirection(VoteDirection.Against)}>
<IonIcon icon={ownVoteDirection == VoteDirection.Against ? thumbsDownSharp : thumbsDownOutline} />
</IonButton> : <IonButton slot="icon-only" onClick={() => updateOwnVoteDirection(VoteDirection.For)}>
<IonIcon icon={ownVoteDirection == VoteDirection.For ? thumbsUpSharp : thumbsUpOutline} />
</IonButton>}
</IonButtons>
We show a thumbs up or thumbs down icon depending on whether this is the for or against messages window. The assumption is that if we’re in the for messages we want to vote for (thumbs up) and if we’re in the against messages we want to vote against (thumbs down).
We show the thumbs up or thumbs down as filled if the current vote matches the thumb direction (e.g. thumbs down is filled if the current vote is against).
When the icon button is clicked, updateOwnVoteDirection
is called with the vote direction of the button that was clicked. This method sets the own vote direction to that of the button if it wasn’t already set otherwise it sets it to undecided:
const updateOwnVoteDirection = (newDirection: VoteDirection) => {
const direction = newDirection != ownVoteDirection ? newDirection : VoteDirection.Undecided;
const vote: IVote = {
...dbEntryDefaults,
direction
};
appData.addVote(vote);
setOwnVoteDirection(direction);
}
Now you can vote on a debate! You can get the full source to the end of this article from the 0.0.3 release branch:
git clone https://github.com/jeremyorme/debate.git
cd debate
git checkout release/0.0.3
Next time we’ll look at how to display the vote counts and add a new page for presentations.
[ad_2]
Source link