Frontend revision 2. Expanded template functionality and convenience

This commit is contained in:
Gnarwhal 2021-01-29 19:52:25 -05:00
parent e5a84268cc
commit 723cd41487
Signed by: Gnarwhal
GPG key ID: 0989A73D8C421174
6 changed files with 142 additions and 89 deletions

View file

@ -9,11 +9,11 @@ public class Achievements {
public static class Achievement { public static class Achievement {
@JsonProperty("Name") @JsonProperty("name")
private String name; private String name;
@JsonProperty("Description") @JsonProperty("description")
private String description; private String description;
@JsonProperty("Stages") @JsonProperty("stages")
private int stages; private int stages;
public Achievement(String name, String description, int stages) { public Achievement(String name, String description, int stages) {
@ -37,11 +37,11 @@ public class Achievements {
// End Getters/Setters // End Getters/Setters
} }
@JsonProperty("GameID") @JsonProperty("gameID")
private int gameID; private int gameID;
@JsonProperty("GameName") @JsonProperty("gameName")
private String gameName; private String gameName;
@JsonProperty("Achievements") @JsonProperty("achievements")
private List<Achievement> achievements; private List<Achievement> achievements;
public Achievements() { achievements = new ArrayList<Achievement>(); } public Achievements() { achievements = new ArrayList<Achievement>(); }

View file

@ -11,9 +11,9 @@ public class Games {
@JsonProperty("ID") @JsonProperty("ID")
private int id; private int id;
@JsonProperty("Name") @JsonProperty("name")
private String name; private String name;
@JsonProperty("Platforms") @JsonProperty("platforms")
private List<String> platforms; private List<String> platforms;
public Game(int id, String name, String platform) { public Game(int id, String name, String platform) {
@ -41,7 +41,7 @@ public class Games {
} }
@JsonProperty("Games") @JsonProperty("games")
private List<Game> games; private List<Game> games;
public Games() { games = new ArrayList<Game>(); } public Games() { games = new ArrayList<Game>(); }

View file

@ -8,24 +8,24 @@
</head> </head>
<body> <body>
<div id="navbar"> <div id="navbar">
<template data-template="navbar: list"> <template data-template="navbar: List<Basic>">
<div id="navbar-section-${navbar[section]}" class="navbar-section"> <div id="navbar-section-${section}" class="navbar-section">
<template data-template="navbar-section-${navbar[section]}: list"> <template data-template="navbar-section-${section}: List<Basic>">
<div id="navbar-item-${navbar-section-${navbar[section]}[item]}" class="navbar-item" data-page-name="${navbar-section-${navbar[section]}[item]}"> <div id="navbar-item-${item}" class="navbar-item" data-page-name="${item}">
${navbar-section-${navbar[section]}[title]} ${title}
</div> </div>
</template> </template>
</div> </div>
</template> </template>
</div> </div>
<div id="content-body"> <div id="content-body">
<template data-template="content-body: list"> <template data-template="content-body: List<Basic>">
<div id="${content-body[page]}-page" class="page"> <div id="${page}-page" class="page">
<div class="page-header"> <div class="page-header">
<p class="page-header-text">${content-body[title]}</p> <p class="page-header-text">${title}</p>
<div class="page-header-separator"></div> <div class="page-header-separator"></div>
</div> </div>
<template data-template="fetch-${content-body[page]}-page: fetch"></template> <template data-template="extern-${page}-page: Extern"></template>
</div> </div>
</template> </template>
</div> </div>

View file

@ -1,24 +1,24 @@
const expandTemplates = async () => { const expandTemplates = async () => {
template.register("navbar", [ template.apply("navbar").values([
{ section: "left" }, { section: "left" },
{ section: "right" } { section: "right" }
]); ]);
template.register("navbar-section-left", [ template.apply("navbar-section-left").values([
{ item: "games", title: "Games" }, { item: "games", title: "Games" },
{ item: "achievements", title: "Achievements" } { item: "achievements", title: "Achievements" }
]); ]);
template.register("navbar-section-right", [ template.apply("navbar-section-right").values([
{ item: "profile", title: "Profile" } { item: "profile", title: "Profile" }
]); ]);
template.register("content-body", [ template.apply("content-body").values([
{ page: "games", title: "Games" }, { page: "games", title: "Games" },
{ page: "achievements", title: "Achievements" }, { page: "achievements", title: "Achievements" },
{ page: "profile", title: "Profile" } { page: "profile", title: "Profile" }
]); ]);
template.register("fetch-games-page", "games_page" ); template.apply("extern-games-page" ).values("games_page" );
template.register("fetch-achievements-page", "achievements_page"); template.apply("extern-achievements-page").values("achievements_page");
template.register("fetch-profile-page", "profile_page" ); template.apply("extern-profile-page" ).values("profile_page" );
template.registerFetch("achievements-page-list", "Achievements", "https://localhost:4730/achievements", { method: 'GET', mode: 'cors' }); template.apply("achievements-page-list" ).fetch("achievements", "https://localhost:4730/achievements");
await template.expand(); await template.expand();
}; };

View file

@ -3,35 +3,57 @@ var template = template || {};
template.type = {}; template.type = {};
template.type._entryMap = new Map(); template.type._entryMap = new Map();
template.type.register = (type, callback) => { template.type.register = (type, callback) => {
const asyncCallback = async function() {
await callback.apply(null, Array.from(arguments));
}
if (typeof type !== 'string') { if (typeof type !== 'string') {
console.error(`'type' must be a string, recieved: `, type); console.error(`'type' must be a string, recieved: `, type);
} else { } else {
if (type === "") { const TYPE_REGEX = /^(\w+)\s*(<\s*\?(?:\s*,\s*\?)*\s*>)?\s*$/;
console.error(`'type' may not be empty`); const result = type.match(TYPE_REGEX);
} else if (template.type._entryMap.get(type) === undefined) { if (result === null) {
template.type._entryMap.set(type, asyncCallback); console.error(`'${type}' is not a valid type id`);
} else { } else {
console.error(`${type} is already a registered template!`); if (result[2] === undefined) {
result[2] = 0;
} else {
result[2] = result[2].split(/\s*,\s*/).length;
}
const completeType = result[1] + ':' + result[2];
if (template.type._entryMap.get(completeType) === undefined) {
template.type._entryMap.set(completeType, async function() {
await callback.apply(null, Array.from(arguments));
});
} else {
console.error(`${type} is already a registered template!`);
}
} }
} }
}; };
// Courtesy of https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&'); // $& means the whole matched string
}
/* Intrinsic Templates */ /* Intrinsic Templates */
// Basic - Simple search and replace // Basic - Simple search and replace
template.type.register('basic', (template, map) => { template.type.register('Basic', (element, map) => {
let html = template.element.innerHTML; let html = element.innerHTML;
for (const key in map) { function applyObject(object, path) {
html = html.replace(new RegExp(`(?:(?<!(?<!\\\\)\\\\)\\\${${key}})`, 'gm'), map[key]); for (const key in object) {
const regexKey = escapeRegExp(path + key);
html = html.replace(new RegExp(`(?:(?<!\\\\)\\\${${regexKey}})`, 'gm'), object[key]);
if (typeof object[key] === 'object') {
applyObject(object[key], path + key + '.');
}
}
} }
template.element.outerHTML = html.trim(); applyObject(map, '');
html = html.replace('\\&', '&');
element.outerHTML = html.trim();
}); });
// Fetch - Retrieve template from webserver // Extern - Retrieve template from webserver
template.type.register('fetch', (template, name) => { template.type.register('Extern', (element, name) => {
return fetch(`templates/${name}.html.template`, { return fetch(`templates/${name}.html.template`, {
method: 'GET', method: 'GET',
mode: 'no-cors', mode: 'no-cors',
@ -39,70 +61,98 @@ template.type.register('fetch', (template, name) => {
'Content-Type': 'text/plain' 'Content-Type': 'text/plain'
} }
}).then(response => response.text().then((data) => { }).then(response => response.text().then((data) => {
template.element.outerHTML = data; element.outerHTML = data;
})).catch(error => { })).catch(error => {
console.error(`failed to retrieve template '${name}': `, error); console.error(`failed to retrieve template '${name}': `, error);
}); });
}); });
// List - Iterate over list and emit copy of child for each iteration // List - Iterate over list and emit copy of child for each iteration
template.type.register('list', (template, arrayMap) => { template.type.register('List<?>', async (element, subtype, arrayMap) => {
let cumulative = ""; let cumulative = '';
for (const map of arrayMap) { const temp = document.createElement('template');
let html = template.element.innerHTML; for (const obj of arrayMap) {
for (const key in map) { temp.innerHTML = `<template></template>`;
html = html.replace(new RegExp(`(?:(?<!(?<!\\\\)\\\\)\\\${${template.id}\\[${key}\\]})`, 'gm'), map[key]); const child = temp.content.children[0];
child.innerHTML = element.innerHTML;
const callback = template.type._entryMap.get(subtype.type);
if (callback === undefined) {
cumulative = '';
console.error(`'${subtype.type}' is not a registered template type`);
} else {
await callback.apply(null, [ child, obj ]);
} }
cumulative = cumulative + html.trim(); cumulative = cumulative + temp.innerHTML.trim();
} }
template.element.outerHTML = cumulative; element.outerHTML = cumulative;
}); });
template._entryMap = new Map(); template._entryMap = new Map();
template.register = function(pattern/*, arguments */) { template.apply = function(pattern, promise) {
if (typeof pattern !== 'string') { if (typeof pattern !== 'string') {
console.error('pattern must be a string, received: ', pattern); console.error('pattern must be a string, received: ', pattern);
} else { } else {
const arrayArguments = Array.from(arguments); return new template.apply.applicators(pattern);
arrayArguments.splice(0, 1);
template._entryMap.set(RegExp("^" + pattern + "$"), async () => arrayArguments);
} }
}; };
template.registerFetch = function(pattern, path, url, fetchOptions) { template.apply.applicators = class {
if (typeof pattern !== 'string') { constructor(pattern) {
console.error('pattern must be a string, received: ', pattern); this._pattern = pattern;
} else { }
_apply(asyncArgs) {
template._entryMap.set(RegExp('^' + this._pattern + '$'), asyncArgs);
}
values(...args) {
this._apply(async () => Array.from(args));
}
promise(promise) {
let args = null; let args = null;
template._entryMap.set(RegExp("^" + pattern + "$"), async () => { promise = promise.then(data => args = [ data ]);
if (args !== null) { this._apply(async () => args || promise);
return Promise.resolve(args); }
} else {
return fetch(url, fetchOptions) fetch(dataProcessor, url, options) {
.then(response => response.json()) if (typeof dataProcessor === 'string') {
.then(data => { const path = dataProcessor;
args = data; dataProcessor = data => {
path = path.split('\\.'); for (const id of path.split(/\./)) {
for (const id of path) { data = data[id];
args = args[id]; if (data === undefined) {
} throw `invalid path '${path}'`;
return args = [ args ]; }
}); }
}
}); return data;
};
};
this.promise(
fetch(url, options || { method: 'GET', mode: 'cors' })
.then(response => response.json())
.then(data => dataProcessor(data))
);
} }
}; };
(() => { (() => {
const parseType = (type) => {
let result = type.match(/^\s*(\w+)\s*(?:<(.*)>)?\s*$/);
let id = result[1];
result = result[2] ? result[2].split(/\s*,\s*/).map(parseType) : [];
return { type: id + ':' + result.length, params: result };
};
const findTemplates = (element) => const findTemplates = (element) =>
Array Array
.from(element.querySelectorAll("template")) .from(element.querySelectorAll('template'))
.filter(child => Boolean(child.dataset.template)) .filter(child => Boolean(child.dataset.template))
.map(child => { .map(child => {
const data = child.dataset.template.split(/\s*:\s*/); const data = child.dataset.template.split(/\s*:\s*/);
return { return {
id: data[0], id: data[0],
type: data.length > 1 ? data[1] : 'basic', typeCapture: parseType(data[1] || 'Begin'),
element: child element: child
}; };
}); });
@ -112,15 +162,18 @@ template.registerFetch = function(pattern, path, url, fetchOptions) {
let promises = []; let promises = [];
let parents = new Set(); let parents = new Set();
for (const child of children) { for (const child of children) {
for (const [pattern, argCallbacks] of template._entryMap) { for (const [pattern, argsCallback] of template._entryMap) {
await argCallbacks().then(args => { await argsCallback().then(args => {
if (pattern.test(child.id)) { if (pattern.test(child.id)) {
const callback = template.type._entryMap.get(child.type); const callback = template.type._entryMap.get(child.typeCapture.type);
if (typeof callback !== 'function') { if (typeof callback !== 'function') {
console.error(`'${child.type}' is not a registered template type`); console.error(`'${child.typeCapture.type}' is not a registered template type`);
} else { } else {
let params = Array.from(args) let params = Array.from(args)
params.splice(0, 0, child); for (const subtype of child.typeCapture.params) {
params.unshift(subtype);
}
params.unshift(child.element);
let parent = child.element.parentElement; let parent = child.element.parentElement;
if (!parents.has(parent)) { if (!parents.has(parent)) {
parents.add(parent); parents.add(parent);
@ -139,7 +192,7 @@ template.registerFetch = function(pattern, path, url, fetchOptions) {
promises.push(expand(parent)); promises.push(expand(parent));
} }
await Promise.all(promises); await Promise.all(promises);
} };
template.expand = async () => expand(document.children[0]); template.expand = async () => expand(document.children[0]);
})(); })();

View file

@ -27,12 +27,12 @@
<p class="achievement-list-page-entry-description">Description</p> <p class="achievement-list-page-entry-description">Description</p>
<p class="achievement-list-page-entry-stages">Stages</p> <p class="achievement-list-page-entry-stages">Stages</p>
</div> </div>
<template data-template="achievements-page-list: list"> <template data-template="achievements-page-list: List<Basic>">
<div class="list-page-list-entry"> <div class="list-page-list-entry">
<img class="achievement-list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img> <img class="achievement-list-page-entry-icon" src="res/dummy_achievement.png" alt="Achievement Thumbnail"></img>
<p class="achievement-list-page-entry-name">${achievements-page-list[Name]}</p> <p class="achievement-list-page-entry-name">${name}</p>
<p class="achievement-list-page-entry-description">${achievements-page-list[Description]}</p> <p class="achievement-list-page-entry-description">${description}</p>
<p class="achievement-list-page-entry-stages">${achievements-page-list[Stages]}</p> <p class="achievement-list-page-entry-stages">${stages}</p>
</div> </div>
</template> </template>
</div> </div>