Files
js/
components/
activity.component.js
activity.list-view.js
libs/
list-container-builder.js
main-component.js
utils.js
index.html
index.js
Source Code
index.html
<div class="_listActivities"></div>
<!-- # templates -->
<template id="tmp-item-activity">
<div data-slot="root" data-kind="item">
<div data-slot="name"></div>
<button data-action="edit">Edit</button>
<button data-action="remove">Remove</button>
</div>
</template>
utils.js
let utils = (function () {
// # self
let SELF = {
DOMSlots,
};
// # function
function DOMSlots(parentNode) {
let slots = {};
[...parentNode.querySelectorAll("[data-slot]"), parentNode].forEach(
(node) => {
let key = node.dataset?.slot;
if (!key || slots[key]) return;
slots[key] = node;
}
);
return slots;
}
return SELF;
})();
libs/list-container-builder.js
function ListContainerBuilder(opt) {
let $ = document.querySelector.bind(document);
let containerEl = opt.container ? $(opt.container) : document.createDocumentFragment();
let templateEl = $(opt.template);
if (templateEl === null) throw new Error(`template not found with selector: ${opt.template}`);
let SELF = {
Refresh,
RefreshSingle,
AppendItems,
SetContainer,
GetContainer,
};
function SetContainer(el) {
containerEl = el;
}
function GetContainer() {
return containerEl;
}
function Refresh(items) {
refreshListContainer(items, containerEl, templateEl, (clonedNode, item) => opt.builder(clonedNode, item));
}
function AppendItems(items) {
let docFrag = document.createDocumentFragment();
for (let item of items) {
let clonedNode = templateEl?.content.cloneNode(true);
opt.builder(clonedNode, item);
docFrag.append(clonedNode);
}
containerEl?.append(docFrag);
}
function RefreshSingle(item) {
let itemEl = opt.lookup?.(containerEl, item);
if (!itemEl) return;
opt.builder(itemEl, item);
}
function refreshListContainer(items, containerEl, templateEl, onItemClone) {
let docFrag = document.createDocumentFragment();
containerEl?.replaceChildren();
if (items?.length > 0) {
for (let item of items) {
let clonedNode = templateEl?.content.cloneNode(true);
let node = onItemClone(clonedNode, item);
if (!node) continue;
docFrag.append(node);
}
}
containerEl?.append(docFrag);
}
return SELF;
}
index.js
{
urls: [
"js/libs/list-container-builder.js",
],
},
{
urls: [
"js/components/activity.list-view.js",
"js/utils.js",
],
},
dom-events.js
let DOMEvents = (function() {
const commonEventTypes = {
onclick: 'click',
ondblclick: 'dblclick',
onmousedown: 'mousedown',
onmouseup: 'mouseup',
onmousemove: 'mousemove',
onmouseover: 'mouseover',
onmouseout: 'mouseout',
ontouchstart: 'touchstart',
onkeypress: 'keypress',
onkeydown: 'keydown',
onkeyup: 'keyup',
onfocus: 'focus',
onblur: 'blur',
oninput: 'input',
onchange: 'change',
onsubmit: 'submit',
onreset: 'reset'
};
function notImplemented() {
console.error('Not implemented')
}
let attachListeners = function(attr, eventType, callbacks, containerEl) {
let elements = containerEl.querySelectorAll(`[${attr}]`);
for (let el of elements) {
let callbackFunc = callbacks?.[el.getAttribute(attr)] ?? notImplemented;
el.addEventListener(eventType, callbackFunc);
}
};
function Listen(eventsMap, containerEl=document) {
let {groupKey} = eventsMap;
let infix = groupKey ? `-${groupKey}` : '';
for (let key in eventsMap) {
if (key == 'groupKey') continue;
let callbackMap = eventsMap[key];
let eventType = callbackMap.eventType ?? getCommonEventType(key);
if (!eventType) {
console.error('Event type not defined:', key);
continue;
}
attachListeners(`data${infix}-${key}`, eventType, callbackMap, containerEl);
}
}
function getCommonEventType(key) {
return commonEventTypes[key];
}
return {
Listen,
};
})();
index.js
import { loadScripts } from "./script-loader.js";
(function () {
loadScripts([
{
urls: [
"js/utils/dom-events.js",
"js/events-map.js"
],
callback: function () {
DOMEvents.Listen(eventsMap);
},
},
/* ... */
]);
})();
main-component.js
function Init() {
listViewActivity.RefreshList();
}
components/activity.list-view.js
let listViewActivity = (function() {
let $ = document.querySelector.bind(document);
let SELF = {
RefreshList,
RefreshSingle,
GetOptions,
SetOptions,
};
// # local
let local = {
states: {
isEventRegistered: false,
},
};
// # list
let listContainer = new ListContainerBuilder({
container: '._listActivities',
template: '#tmp-item-activity',
builder: (node, item) => buildListItem(node, item),
lookup: (containerEl, item) => containerEl.querySelector(`[data-id="${item.id}"]`),
});
// # function
function GetOptions() {
return local.options;
}
function SetOptions(options) {
for (let key in options) {
if (typeof(local.options[key]) != 'undefined') {
local.options[key] = options[key];
}
}
}
function registerEventListeners() {
if (local.states.isEventRegistered) return;
local.states.isEventRegistered = true;
let container = listContainer.GetContainer();
container.addEventListener('click', HandleClickEvt);
}
function HandleClickEvt(evt) {
let targetEl = evt.target;
let itemEl = targetEl?.closest('[data-kind="item"]');
let action = targetEl?.closest('[data-action]')?.dataset.action;
if (!itemEl) return;
handleClickAction(itemEl, action);
}
// # dom events, # events
function handleClickAction(itemEl, action) {
let data = {
id: itemEl.dataset.id,
};
let {id} = data;
switch (action) {
case 'edit': compoActivity.Edit(id); break;
case 'remove': compoActivity.Remove(id); break;
}
}
// # refresh
function RefreshList() {
registerEventListeners();
let items = servActivities.GetAll();
listContainer.Refresh(items);
}
// # build
function buildListItem(node, item) {
let slots = utils.DOMSlots(node);
let itemEl = slots.root;
let {id, name} = item;
slots.root.dataset.id = id;
slots.name?.replaceChildren(name);
return itemEl;
}
function RefreshSingle(id) {
let item = servActivities.GetById(id);
listContainer.RefreshSingle(item);
}
return SELF;
})();
components/activity.component.js
let SELF = {
Edit,
Remove,
};
// # function
function Edit(id) {
let item = servActivities.GetById(id);
let {name} = item;
let userVal = window.prompt('Activity name', name);
if (userVal === null) return;
item.name = userVal;
servActivities.Save_();
listViewActivity.RefreshSingle(id);
}
function Remove(id) {
let item = servActivities.GetById(id);
let {name} = item;
let isConfirm = window.confirm(`Delete activity: ${name}?`);
if (!isConfirm) return;
servActivities.Remove(id)
servActivities.Save_();
listViewActivity.RefreshList();
}
https://vanillawebdev.blogspot.com/2024/12/list-view-ui-components-setup.html
https://www.blogger.com/blog/post/edit/8166404610182826392/2743187008898833501
https://www.blogger.com/blog/page/edit/8166404610182826392/2743187008898833501
List View UI Components Setup
December 12, 2024
Files
js/
components/
activity.component.js
activity.list-view.js
libs/
list-container-builder.js
main-component.js
utils.js
index.html
index.js
Source Code
index.html
<div class="_listActivities"></div>
<!-- # templates -->
<template id="tmp-item-activity">
<div data-slot="root" data-kind="item">
<div data-slot="name"></div>
<button data-action="edit">Edit</button>
<button data-action="remove">Remove</button>
</div>
</template>
utils.js
let utils = (function () {
// # self
let SELF = {
DOMSlots,
};
// # function
function DOMSlots(parentNode) {
let slots = {};
[...parentNode.querySelectorAll("[data-slot]"), parentNode].forEach(
(node) => {
let key = node.dataset?.slot;
if (!key || slots[key]) return;
slots[key] = node;
}
);
return slots;
}
return SELF;
})();
libs/list-container-builder.js
function ListContainerBuilder(opt) {
let $ = document.querySelector.bind(document);
let containerEl = opt.container ? $(opt.container) : document.createDocumentFragment();
let templateEl = $(opt.template);
if (templateEl === null) throw new Error(`template not found with selector: ${opt.template}`);
let SELF = {
Refresh,
RefreshSingle,
AppendItems,
SetContainer,
GetContainer,
};
function SetContainer(el) {
containerEl = el;
}
function GetContainer() {
return containerEl;
}
function Refresh(items) {
refreshListContainer(items, containerEl, templateEl, (clonedNode, item) => opt.builder(clonedNode, item));
}
function AppendItems(items) {
let docFrag = document.createDocumentFragment();
for (let item of items) {
let clonedNode = templateEl?.content.cloneNode(true);
opt.builder(clonedNode, item);
docFrag.append(clonedNode);
}
containerEl?.append(docFrag);
}
function RefreshSingle(item) {
let itemEl = opt.lookup?.(containerEl, item);
if (!itemEl) return;
opt.builder(itemEl, item);
}
function refreshListContainer(items, containerEl, templateEl, onItemClone) {
let docFrag = document.createDocumentFragment();
containerEl?.replaceChildren();
if (items?.length > 0) {
for (let item of items) {
let clonedNode = templateEl?.content.cloneNode(true);
let node = onItemClone(clonedNode, item);
if (!node) continue;
docFrag.append(node);
}
}
containerEl?.append(docFrag);
}
return SELF;
}
index.js
{
urls: [
"js/libs/list-container-builder.js",
],
},
{
urls: [
"js/components/activity.list-view.js",
"js/utils.js",
],
},
dom-events.js
let DOMEvents = (function() {
const commonEventTypes = {
onclick: 'click',
ondblclick: 'dblclick',
onmousedown: 'mousedown',
onmouseup: 'mouseup',
onmousemove: 'mousemove',
onmouseover: 'mouseover',
onmouseout: 'mouseout',
ontouchstart: 'touchstart',
onkeypress: 'keypress',
onkeydown: 'keydown',
onkeyup: 'keyup',
onfocus: 'focus',
onblur: 'blur',
oninput: 'input',
onchange: 'change',
onsubmit: 'submit',
onreset: 'reset'
};
function notImplemented() {
console.error('Not implemented')
}
let attachListeners = function(attr, eventType, callbacks, containerEl) {
let elements = containerEl.querySelectorAll(`[${attr}]`);
for (let el of elements) {
let callbackFunc = callbacks?.[el.getAttribute(attr)] ?? notImplemented;
el.addEventListener(eventType, callbackFunc);
}
};
function Listen(eventsMap, containerEl=document) {
let {groupKey} = eventsMap;
let infix = groupKey ? `-${groupKey}` : '';
for (let key in eventsMap) {
if (key == 'groupKey') continue;
let callbackMap = eventsMap[key];
let eventType = callbackMap.eventType ?? getCommonEventType(key);
if (!eventType) {
console.error('Event type not defined:', key);
continue;
}
attachListeners(`data${infix}-${key}`, eventType, callbackMap, containerEl);
}
}
function getCommonEventType(key) {
return commonEventTypes[key];
}
return {
Listen,
};
})();
index.js
import { loadScripts } from "./script-loader.js";
(function () {
loadScripts([
{
urls: [
"js/utils/dom-events.js",
"js/events-map.js"
],
callback: function () {
DOMEvents.Listen(eventsMap);
},
},
/* ... */
]);
})();
main-component.js
function Init() {
listViewActivity.RefreshList();
}
components/activity.list-view.js
let listViewActivity = (function() {
let $ = document.querySelector.bind(document);
let SELF = {
RefreshList,
RefreshSingle,
GetOptions,
SetOptions,
};
// # local
let local = {
states: {
isEventRegistered: false,
},
};
// # list
let listContainer = new ListContainerBuilder({
container: '._listActivities',
template: '#tmp-item-activity',
builder: (node, item) => buildListItem(node, item),
lookup: (containerEl, item) => containerEl.querySelector(`[data-id="${item.id}"]`),
});
// # function
function GetOptions() {
return local.options;
}
function SetOptions(options) {
for (let key in options) {
if (typeof(local.options[key]) != 'undefined') {
local.options[key] = options[key];
}
}
}
function registerEventListeners() {
if (local.states.isEventRegistered) return;
local.states.isEventRegistered = true;
let container = listContainer.GetContainer();
container.addEventListener('click', HandleClickEvt);
}
function HandleClickEvt(evt) {
let targetEl = evt.target;
let itemEl = targetEl?.closest('[data-kind="item"]');
let action = targetEl?.closest('[data-action]')?.dataset.action;
if (!itemEl) return;
handleClickAction(itemEl, action);
}
// # dom events, # events
function handleClickAction(itemEl, action) {
let data = {
id: itemEl.dataset.id,
};
let {id} = data;
switch (action) {
case 'edit': compoActivity.Edit(id); break;
case 'remove': compoActivity.Remove(id); break;
}
}
// # refresh
function RefreshList() {
registerEventListeners();
let items = servActivities.GetAll();
listContainer.Refresh(items);
}
// # build
function buildListItem(node, item) {
let slots = utils.DOMSlots(node);
let itemEl = slots.root;
let {id, name} = item;
slots.root.dataset.id = id;
slots.name?.replaceChildren(name);
return itemEl;
}
function RefreshSingle(id) {
let item = servActivities.GetById(id);
listContainer.RefreshSingle(item);
}
return SELF;
})();
components/activity.component.js
let SELF = {
Edit,
Remove,
};
// # function
function Edit(id) {
let item = servActivities.GetById(id);
let {name} = item;
let userVal = window.prompt('Activity name', name);
if (userVal === null) return;
item.name = userVal;
servActivities.Save_();
listViewActivity.RefreshSingle(id);
}
function Remove(id) {
let item = servActivities.GetById(id);
let {name} = item;
let isConfirm = window.confirm(`Delete activity: ${name}?`);
if (!isConfirm) return;
servActivities.Remove(id)
servActivities.Save_();
listViewActivity.RefreshList();
}
Comments
Post a Comment