the
(unofficial) melonland FFXIV topic sent me down a
rabbit viera hole... i started thinking, "why am i linking people to my character's page on the lodestone, which is kind of ugly? i have a website, can't i make my own page?"
2024/01/17 update: it's broken :(this led me to
XIVAPI, which lets you access game data in a JSON format. before this week, i had no experience with javascript and no idea what JSON was, but with the help of this
mdn web docs guide, i was able to put something simple together.
here's the javascript for retrieving your character's name, active class, and class levels (broken up into subgroups, because the list ends up way too long for styling purposes otherwise):
async function populate() {
const requestURL = 'https://xivapi.com/character/37508137'; // put your character ID here! //
const request = new Request(requestURL);
const response = await fetch(request);
const charInfo = await response.json();
populateHeader(charInfo);
populateTank(charInfo);
populateHealer(charInfo);
populateMelee(charInfo);
populateRanged(charInfo);
populateMage(charInfo);
populateCrafter(charInfo);
populateGatherer(charInfo);
}
function populateHeader(obj) {
const header = document.querySelector('header');
const myH1 = document.createElement('h1');
myH1.textContent = obj.Character.Name;
header.appendChild(myH1);
const myH2 = document.createElement('h2');
myH2.textContent = `Active Class: ${obj.Character.ActiveClassJob.UnlockedState.Name}`;
header.appendChild(myH2);
}
function populateTank(obj) {
const section = document.getElementById("tanks");
const tanks = obj.Character.ClassJobs.slice(0, 4);
for (const tank of tanks) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${tank.UnlockedState.Name}: ${tank.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
function populateHealer(obj) {
const section = document.getElementById("healers");
const healers = obj.Character.ClassJobs.slice(4, 8);
for (const healer of healers) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${healer.UnlockedState.Name}: ${healer.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
function populateMelee(obj) {
const section = document.getElementById("melee");
const melees = obj.Character.ClassJobs.slice(8, 13);
for (const melee of melees) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${melee.UnlockedState.Name}: ${melee.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
function populateRanged(obj) {
const section = document.getElementById("ranged");
const rangeds = obj.Character.ClassJobs.slice(13, 16);
for (const ranged of rangeds) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${ranged.UnlockedState.Name}: ${ranged.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
function populateMage(obj) {
const section = document.getElementById("mages");
const mages = obj.Character.ClassJobs.slice(16, 20);
for (const mage of mages) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${mage.UnlockedState.Name}: ${mage.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
function populateCrafter(obj) {
const section = document.getElementById("crafters");
const crafters = obj.Character.ClassJobs.slice(20, 28);
for (const crafter of crafters) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${crafter.UnlockedState.Name}: ${crafter.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
function populateGatherer(obj) {
const section = document.getElementById("gatherers");
const gatherers = obj.Character.ClassJobs.slice(28, 31);
for (const gatherer of gatherers) {
const myArticle = document.createElement('article');
const myPara1 = document.createElement('p');
myPara1.textContent = `${gatherer.UnlockedState.Name}: ${gatherer.Level}`;
myArticle.appendChild(myPara1);
section.appendChild(myArticle);
}
}
populate();
and then in your page's <body></body> you could put something like this:
<header></header>
<div id="tanks">
<h3>Tanks:</h3>
</div>
<div id="healers">
<h3>Healers:</h3>
</div>
<div id="melee">
<h3>Melee:</h3>
</div>
<div id="ranged">
<h3>Ranged:</h3>
</div>
<div id="mages">
<h3>Casters:</h3>
</div>
<div id="crafters">
<h3>Crafters:</h3>
</div>
<div id="gatherers">
<h3>Gatherers:</h3>
</div>
is this totally redundant to the lodestone? yes. would it be easier manually update my character's levels? also yes. but i learned something new! and it's fully customizable
i still have a lot to learn, like how to display achievement, mount, and minion data, but i figured i'd share what i have so far in case anyone here is interested in doing something similar.