Another View: Binding Myself to Vue.js
I’ve been slowly been learning Vue.js, relating to toy projects. Nothing earthshattering, but helpful in my other hobbies.
In this case, the hobby is Cyberpunk 2020, an RPG and source for the video game Cyberpunk 2077. In those rules, there are skills, and you have 40 points for Career skills and between 2 and 20 points for Pickup skills. The thing is, not all skills are the same: there’s a difficulty modifier, saying that, for example, learning Tae Kwon Do is exactly four times as hard as learning Danish. : ¯\_(ツ)_/¯
I created a JSON file with all the by-the-book skills and values (handwaving Expert, which would allow your character to know everything about, for example, Baseball stats. Works as a roll-playing hook, I suppose.) I got to where I could display the whole list in formatted HTML with Vue, with a SELECT box with a blank and numbers between 1 and 7. At 8 and above, you’re getting into world-class at what you do. If a pilot, you’re Maverick. IF a fighter, you’re Bruce Lee. If a fixer, you’re Tony Montana, halfway through the movie. If you’re media, you’re Edison Carter, or maybe Max Headroom. It is suggested to GMs that you don’t let your players start out as the best at what they do when as new characters, they have zero rep.
The problem is that counting those would be the straight skill value, while skill points used = skill value * difficulty modifier
, and doing that in my head with a large number of skills, and I don’t want to use the scratch paper. This is why I wanted to make the skill tool.
The examples I’m seeing set it up so that you can change a form value and see it show up on the page.
<!-- Handling User Input- https://vuejs.org/v2/guide/ -->
<div id="app-6">
<p>\{\{ message }}</p>
<input v-model="message" />
</div>
What I wanted was to take all those anonymous, generated selects, sum the values, and put that value somewhere convenient, so I can always tell that I’ve used so many skill points without having to do that multiplication in my head. The problem with the above code is that it’s anonymous, making it hard to interact with directly.
In this case, I eventually learned that you can make an element and give it an @click
attribute, where you name a method
for it to do. Eventually, I learned you can also add @change
, which makes it very useful with my SELECTs.
@click
can be placed on a button — and in HTML, what can’t be a button? — to make it run that method. @change
sets it to the element’s onchange
event handler. If we’re talking most elements, they don’t change often, but for SELECTs? Exactly what I need.
I admit, for this next step, I went with my fairly old and vanilla JS, rather than learn the hot new Vue way of thinking through this. I’m learning things one at a time, not all at once, OK?
Here’s an abbreviated skill list in JSON. I feel I should explain that, with Cyberpunk, there’s style as well as substance. You shouldn’t just be good, but you should look good doing it. Thus there is an Attractiveness
{
"skills": {
"attr": [
{
"name": "Personal Grooming",
"value": 1
},
{
"name": "Wardrobe & Style",
"value": 1
}
]
}
}
<!DOCTYPE html>
<html>
<head>
<title>Cyberpunk 2020 Skills</title>
</head>
<body>
<div id="app">
<div id="skills">
<div>
<h3>TOTAL</h3>
<dl>
<dt><b>Count of all Career and Pickup points:</b></dt>
<dd id="totalCount">0</dd>
</dl>
</div>
<div v-for="(value1,name1) in all">
<h3>\{\{ name1.toUpperCase() }}</h3>
<dl v-for="(value2,name2) in value1">
<dt>
<b>\{\{ value2.name }} (\{\{ value2.value }})</b>
</dt>
<dd>
<select v-bind:mult="value2.value" @change="total_up">
<!-- This could be generated as well -->
<option></option>
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
</select>
</dd>
</dl>
</div>
</div>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14"></script>
<script>
const app = new Vue({
el: "#app",
data: {
all: {},
data: {},
},
methods: {
total_up() {
let tc = document.getElementById("totalCount");
let selects = document.getElementsByTagName("select");
let sum = 0;
for (let s of selects) {
let mult = s.getAttribute("mult");
let m = parseInt(mult);
let i = s.selectedIndex;
let o = s[i].innerHTML;
if (o !== "") {
let n = parseInt(o, 10);
let mn = n * m;
sum += mn;
}
}
tc.innerHTML = sum;
},
},
created() {
fetch("https://path/to/cp_skills.json")
.then((response) => response.json())
.then((data) => {
this.all = data.skills;
});
},
});
</script>
</html>
Right off, the total_up
method code is not as I would want to be. I tried to do some arrow function fun, basically getting s.value
, but I could not force it to work, instead getting s.selectedIndex
and pulling that from the OPTION list. I’m not happy with this solution, but I am satisfied.
ETA
I suppose I should hold off submitting before I’m done with it.
The key, I believe, is to make an array out of the collection of elements, that, when you use typeof
, says object
. Once then, I use a bunch of arrow functions to get things down to the things I want (or zero), then make an all-index array, which I use to map
and multiply the rest, then reduce
to the sum.
Never give up! Never surrender!
total_up() {
let tc = document.getElementById("totalCount");
let selects = document.getElementsByTagName("select");
// convert the selects obj to an array
let array = [];
for (let i of selects) {
array.push(i);
}
// array function to convert an array of elements
// to all set values
let values = array
.map((s) => s.value)
.map((s) => (s !== "" ? s : 0))
.map((s) => parseInt(s));
// array function to convert an array of elements
// to all set modifiers
let modifiers = array
.map((s) => s.getAttribute("mult"))
.map((s) => parseInt(s));
// array function to multiply value vs modifier
// and sum them
let result = new Array(array.length)
.fill()
.map((n, i) => i)
.map((i) => values[i] * modifiers[i])
.reduce((i, j) => i + j, 0);
tc.innerHTML = result;
},