Frontend revision 2. Expanded template functionality and convenience
This commit is contained in:
parent
e5a84268cc
commit
723cd41487
6 changed files with 142 additions and 89 deletions
|
@ -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>(); }
|
||||||
|
|
|
@ -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>(); }
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 {
|
||||||
|
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 {
|
} else {
|
||||||
console.error(`${type} is already a registered template!`);
|
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) {
|
||||||
|
if (typeof dataProcessor === 'string') {
|
||||||
|
const path = dataProcessor;
|
||||||
|
dataProcessor = data => {
|
||||||
|
for (const id of path.split(/\./)) {
|
||||||
|
data = data[id];
|
||||||
|
if (data === undefined) {
|
||||||
|
throw `invalid path '${path}'`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
this.promise(
|
||||||
|
fetch(url, options || { method: 'GET', mode: 'cors' })
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => dataProcessor(data))
|
||||||
args = data;
|
);
|
||||||
path = path.split('\\.');
|
|
||||||
for (const id of path) {
|
|
||||||
args = args[id];
|
|
||||||
}
|
|
||||||
return args = [ args ];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
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]);
|
||||||
})();
|
})();
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue