kempo 2.7.4 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Table.js +1 -1
- package/docs/components/table.html +259 -151
- package/docs/icons/add.svg +1 -0
- package/docs/icons/refresh.svg +1 -0
- package/docs/kempo/components/Table.js +1 -1
- package/icons/add.svg +1 -0
- package/icons/refresh.svg +1 -0
- package/package.json +1 -1
- package/src/components/Table.js +383 -34
- package/README.md +0 -2
package/dist/components/Table.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import Component from"./Component.js";import Icon from"./Icon.js";import{toTitleCase}from"../utils/string.js";export default class Table extends Component{
|
|
1
|
+
import Component from"./Component.js";import Icon from"./Icon.js";import{toTitleCase}from"../utils/string.js";import{onEvent,dispatchEvent}from"../utils/element.js";export default class Table extends Component{static controls={prevPage:{render:e=>{const t=document.createElement("button");return t.classList.add("pq"),t.appendChild(new Icon("chevron-left")),onEvent(t,"click",(()=>e.prevPage())),onEvent(e,"pageChange",(()=>{t.disabled=1===e.getCurrentPage()})),t}},nextPage:{render:e=>{const t=document.createElement("button");return t.classList.add("pq"),t.appendChild(new Icon("chevron-right")),onEvent(t,"click",(()=>e.nextPage())),onEvent(e,"pageChange",(()=>{t.disabled=e.getCurrentPage()===e.getTotalPages()})),t}},pageSelect:{render:e=>new PageSelect(e)},pageSize:{render:e=>new PageSize(e)},spacer:{html:'<div class="flex"></div>'}};constructor({records:e=[],fields:t=[],controls:n={before:[],after:[],top:[],bottom:[]},pageSize:s=100,pageSizeOptions:r=[10,25,50,100,500],currentPage:a=1}={}){super(),this.registerProps({fields:t,records:e,controls:n,pageSize:s,pageSizeOptions:r,currentPage:a})}async render(e){return!!await super.render(e)&&(this.setData({records:this.records,fields:this.fields,controls:this.controls,pageSize:this.pageSize,pageSizeOptions:this.pageSizeOptions,currentPage:this.currentPage}),!0)}renderFields(){if(!this.rendered)return;const e=this.controls?.before?.length?"<th></th>":"",t=this.controls?.after?.length?"<th></th>":"";this.shadowRoot.getElementById("fields").innerHTML=`${e}${this.fields.map((({label:e})=>`<th>${e}</th>`)).join("")}${t}`}renderRecords(){if(!this.rendered)return;const e=this.shadowRoot.getElementById("records");e.innerHTML="";const t=(this.currentPage-1)*this.pageSize,n=t+this.pageSize;this.records.slice(t,n).forEach(((s,r)=>{const a=document.createElement("tr");this.controls?.before?.length&&a.appendChild(this.renderRowControls(this.controls.before,s,t+r)),this.fields.forEach((({name:e,formatter:r,calculator:o})=>{const i=document.createElement("td");if(null===s)i.innerHTML="Loading...",dispatchEvent(this,"fetchRecords",{start:t,end:n});else{let t;o?t=o(s,this):(t=s[e]||"",Array.isArray(t)?t=r?t.map(r).join(", "):t.join(", "):r&&(t=r(t))),i.innerHTML=t}a.appendChild(i)})),this.controls?.after?.length&&a.appendChild(this.renderRowControls(this.controls.after,s,t+r)),e.appendChild(a)}))}renderRowControls(e=[],t,n){if(!this.rendered)return;const s=document.createElement("td");return e.forEach((({html:e,icon:r,action:a,render:o})=>{if(e)s.appendChild(document.createRange().createContextualFragment(e));else if(o&&"function"==typeof o){const e=o(this,t,n);e instanceof HTMLElement?s.appendChild(e):"string"==typeof e&&s.appendChild(document.createRange().createContextualFragment(e))}else if(r){const e=document.createElement("button");e.classList.add("pq"),e.appendChild(new Icon(r)),a&&onEvent(e,"click",(()=>a(this,t,n))),s.appendChild(e)}})),s}renderControls(e=[],t){if(!this.rendered)return;const n=this.shadowRoot.getElementById(t);n.innerHTML="",e.forEach((({html:e,icon:t,action:s,render:r})=>{if(e)n.appendChild(document.createRange().createContextualFragment(e));else if(r&&"function"==typeof r){const e=r(this);e instanceof HTMLElement?n.appendChild(e):"string"==typeof e&&n.appendChild(document.createRange().createContextualFragment(e))}else if(t){const e=document.createElement("button");e.classList.add("pq"),e.appendChild(new Icon(t)),s&&onEvent(e,"click",(()=>s(this))),n.appendChild(e)}}))}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.records.length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.renderRecords(),dispatchEvent(this,"pageChange"))}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}setPageSize(e){this.pageSize=e,this.currentPage=1,this.renderRecords(),dispatchEvent(this,"pageSizeChange")}setData({records:e=!1,fields:t=!1,controls:n=!1,pageSize:s=!1,pageSizeOptions:r=!1,currentPage:a=!1}={}){let o=!1,i=!1;e&&(this.records=e,this.fields=t||Table.extractFieldsFromRecords(this.records),o=!0),s&&(this.pageSize=s,o=!0),r&&(this.pageSizeOptions=r),n&&(this.controls=n,i=!0),a&&(this.currentPage=a,o=!0),o&&(this.renderFields(),this.renderRecords()),i&&(this.renderControls(this.controls.top,"top"),this.renderControls(this.controls.bottom,"bottom"))}setRecords(e){this.records=e,this.currentPage=1,this.renderRecords(),dispatchEvent(this,"recordsSet",{records:e})}setupFetchRecords(e,t){const n=this.records.length;n<e&&(this.records.length=e,this.records.fill(null,n)),onEvent(this,"fetchRecords",(async e=>{const{start:n,end:s}=e.detail,r=await t(n,s-n);this.records.splice(n,r.length,...r),this.renderRecords()}))}addRecord(e){this.records.push(e),this.renderRecords(),dispatchEvent(this,"recordAdded",{record:e})}updateRecord(e,t){this.records[e]=t;const n=(this.currentPage-1)*this.pageSize,s=n+this.pageSize;e>=n&&e<s&&this.renderRecords()}deleteRecord(e){e>=0&&e<this.records.length&&(this.records.splice(e,1),this.renderRecords(),dispatchEvent(this,"recordDeleted",{index:e}))}get shadowTemplate(){return`\n ${super.shadowTemplate}\n <div class="responsive-table">\n <div id="top"></div>\n <table class="b0">\n <thead id="fields"></thead>\n <tbody id="records"></tbody>\n </table>\n <div id="bottom"></div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n margin-bottom: var(--spacer);\n border: 1px solid var(--c_border);\n border-radius: var(--radius);\n }\n #top, #bottom {\n display: flex;\n }\n th {\n border-top: none;\n }\n tr:last-child td:first-child {\n border-bottom-left-radius: 0;\n }\n tr:last-child td:last-child {\n border-bottom-right-radius: 0;\n }\n tr:last-child td {\n border-bottom: none;\n }\n th:first-child, td:first-child {\n border-left: none;\n }\n th:last-child, td:last-child {\n border-right: none;\n }\n #top:not(:empty) {\n border-bottom: 1px solid var(--c_border);\n }\n #bottom:not(:empty) {\n border-top: 1px solid var(--c_border);\n }\n `}static extractFieldsFromRecords(e,t=100){const n=new Set;return e.slice(0,t).forEach((e=>{Object.keys(e).forEach((e=>n.add(e)))})),[...n].map((e=>({name:e,label:toTitleCase(e)})))}}window.customElements.define("k-table",Table);class PageSelect extends Component{constructor(e){super(),this.table=e,this.classList.add("mxq")}connectedCallback(){this.render()}async render(){return!!await super.render()&&(this.updateOptions(),!0)}updateOptions(){const e=this.shadowRoot.getElementById("pageSelect");e.innerHTML="";for(let t=1;t<=this.table.getTotalPages();t++){const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)}e.value=this.table.getCurrentPage();this.shadowRoot.getElementById("totalPages").textContent=this.table.getTotalPages(),onEvent(e,"change",(()=>{this.table.setPage(parseInt(e.value))})),onEvent(this.table,"pageSizeChange",(()=>{this.updateOptions()})),onEvent(this.table,"pageChange",(()=>{e.value=this.table.getCurrentPage()}))}get shadowTemplate(){return'\n <select id="pageSelect" class="mxq"></select>\n <label> out of <span id="totalPages"></span></label>\n '}get shadowStyles(){return"\n :host {\n display: inline-flex;\n width: max-content;\n align-items: baseline;\n }\n #pageSelect, label {\n display: inline;\n }\n label {\n white-space: nowrap;\n }\n "}}window.customElements.define("k-table-page-select",PageSelect);class PageSize extends Component{constructor(e){super(),this.table=e,this.classList.add("mxq")}connectedCallback(){this.render()}async render(){if(await super.render()){const e=this.shadowRoot.getElementById("pageSizeSelect");return this.table.pageSizeOptions.forEach((t=>{const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)})),e.value=this.table.pageSize,onEvent(e,"change",(()=>{this.table.setPageSize(parseInt(e.value))})),onEvent(this.table,"pageSizeChange",(()=>{e.value=this.table.pageSize})),!0}return!1}get shadowTemplate(){return'\n <label for="pageSizeSelect">Page Size: </label>\n <select id="pageSizeSelect" class="mxq"></select>\n '}get shadowStyles(){return"\n :host {\n display: inline-flex;\n width: max-content;\n align-items: baseline;\n }\n label {\n white-space: nowrap;\n }\n "}}window.customElements.define("k-table-page-size",PageSize);
|
|
@@ -6,6 +6,45 @@
|
|
|
6
6
|
<title>Table - Components - Kempo Docs - A Web Components Solution</title>
|
|
7
7
|
<link rel="stylesheet" href="../kempo/kempo-vars.css" /><link rel="stylesheet" href="../kempo/kempo-styles.css" />
|
|
8
8
|
<link rel="stylesheet" href="../kempo/kempo-hljs.css" />
|
|
9
|
+
<script type="module" src="../kempo/components/Table.js"></script>
|
|
10
|
+
<script>
|
|
11
|
+
|
|
12
|
+
window.contacts = [];
|
|
13
|
+
function generateContact(){
|
|
14
|
+
const maleNames = [
|
|
15
|
+
"Liam", "Noah", "Oliver", "Elijah", "William", "James", "Benjamin",
|
|
16
|
+
"Lucas", "Henry", "Alexander", "Mason", "Michael", "Ethan", "Daniel",
|
|
17
|
+
"Jacob", "Logan", "Jackson", "Levi", "Sebastian", "Mateo", "Jack",
|
|
18
|
+
"Owen", "Theodore", "Aiden", "Samuel", "Joseph", "John", "David",
|
|
19
|
+
"Wyatt", "Matthew"
|
|
20
|
+
];
|
|
21
|
+
const femaleNames = [
|
|
22
|
+
"Olivia", "Emma", "Ava", "Sophia", "Isabella", "Mia", "Amelia",
|
|
23
|
+
"Harper", "Evelyn", "Abigail", "Emily", "Ella", "Elizabeth",
|
|
24
|
+
"Camila", "Luna", "Sofia", "Avery", "Mila", "Aria", "Scarlett",
|
|
25
|
+
"Penelope", "Layla", "Chloe", "Victoria", "Madison", "Eleanor",
|
|
26
|
+
"Grace", "Nora", "Riley", "Zoey"
|
|
27
|
+
];
|
|
28
|
+
const lastNames = [
|
|
29
|
+
"Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
|
|
30
|
+
"Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez",
|
|
31
|
+
"Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
|
|
32
|
+
"Lee", "Perez", "Thompson", "White", "Harris", "Sanchez", "Clark",
|
|
33
|
+
"Ramirez", "Lewis", "Robinson"
|
|
34
|
+
];
|
|
35
|
+
const gender = Math.random() > 0.5 ? 'm' : 'f';
|
|
36
|
+
const firstName = gender === 'm' ? maleNames[Math.floor(Math.random() * maleNames.length)] : femaleNames[Math.floor(Math.random() * femaleNames.length)];
|
|
37
|
+
const lastName = lastNames[Math.floor(Math.random() * lastNames.length)];
|
|
38
|
+
const name = `${firstName} ${lastName}`;
|
|
39
|
+
const email = `${firstName.toLowerCase()}.${lastName.toLowerCase()}@mailserver.com`;
|
|
40
|
+
const birthday = `${Math.floor(Math.random() * 100) + 1920}-${String(Math.floor(Math.random() * 12) + 1).padStart(2, '0')}-${String(Math.floor(Math.random() * 28) + 1).padStart(2, '0')}`;
|
|
41
|
+
const phoneNumber = `(${Math.floor(Math.random() * 900) + 100}) ${Math.floor(Math.random() * 900) + 100}-${Math.floor(Math.random() * 9000) + 1000}`;
|
|
42
|
+
return { name, email, birthday, phoneNumber, gender };
|
|
43
|
+
}
|
|
44
|
+
for (let i = 0; i < 100; i++) {
|
|
45
|
+
contacts.push(generateContact());
|
|
46
|
+
};
|
|
47
|
+
</script>
|
|
9
48
|
</head>
|
|
10
49
|
<body>
|
|
11
50
|
<k-import src="../nav-1.inc.html"></k-import>
|
|
@@ -16,13 +55,17 @@
|
|
|
16
55
|
>
|
|
17
56
|
<summary class="p">Table of Contents</summary>
|
|
18
57
|
<div class="m mt0 pl">
|
|
58
|
+
<h6>Examples</h6>
|
|
19
59
|
<a href="#basicUsage">Basic Usage</a><br />
|
|
20
60
|
<a href="#customFields">Custom Fields</a><br />
|
|
61
|
+
<a href="#topBottomControls">Top and Bottom Controls</a><br />
|
|
62
|
+
<a href="#rowControls">Row Controls</a><br />
|
|
63
|
+
<a href="#pagination">Pagination</a><br />
|
|
64
|
+
<h6 class="mt">JavaScript Reference</h6>
|
|
21
65
|
<a href="#constructor">Constructor</a><br />
|
|
22
66
|
<a href="#requirements">Requirements</a><br />
|
|
23
67
|
<a href="#properties">Properties</a><br />
|
|
24
68
|
<a href="#methods">Methods</a><br />
|
|
25
|
-
<a href="#controls">Row Controls</a><br />
|
|
26
69
|
</div>
|
|
27
70
|
</details>
|
|
28
71
|
|
|
@@ -30,151 +73,207 @@
|
|
|
30
73
|
<p>The <code><k-table></code> component is used to render data into a <code><table></code> using JavaScript.</p>
|
|
31
74
|
|
|
32
75
|
<h3 id="basicUsage"><a href="#basicUsage" class="no-link">Basic Usage</a></h3>
|
|
33
|
-
<p>In HTML create a <code><k-table></code>, and then in JavaScript call the <code>setData</code> method passing in an
|
|
76
|
+
<p>In HTML create a <code><k-table></code>, and then in JavaScript call the <code>setData</code> method passing in an options object containing your data.</p>
|
|
34
77
|
|
|
35
|
-
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"
|
|
36
|
-
<
|
|
37
|
-
<k-table id="contacts"></k-table>
|
|
78
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"basicUsageExample"</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br /><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript"><br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'basicUsageExample'</span>).setData({<br /> records: [<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Dustin"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(111) 111-1111"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"dustin@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Male"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Kayla"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(222) 222-2222"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"kayla@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Female"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Alexander"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(333) 333-33333"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"alex@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Male"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"Amelia"</span>,<br /> <span class="hljs-attr">phoneNumber</span>: <span class="hljs-string">"(444) 444-44444"</span>,<br /> <span class="hljs-attr">emailAddress</span>: <span class="hljs-string">"amelia@mailserver.com"</span>,<br /> <span class="hljs-attr">gender</span>: <span class="hljs-string">"Female"</span><br /> },<br /> ]<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
79
|
+
<k-table id="basicUsageExample"></k-table>
|
|
38
80
|
<script type="module">
|
|
39
|
-
document.getElementById('
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
81
|
+
document.getElementById('basicUsageExample').setData({
|
|
82
|
+
records: [
|
|
83
|
+
{
|
|
84
|
+
name: "Dustin",
|
|
85
|
+
phoneNumber: "(111) 111-1111",
|
|
86
|
+
emailAddress: "dustin@mailserver.com",
|
|
87
|
+
gender: "Male"
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
name: "Kayla",
|
|
91
|
+
phoneNumber: "(222) 222-2222",
|
|
92
|
+
emailAddress: "kayla@mailserver.com",
|
|
93
|
+
gender: "Female"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "Alexander",
|
|
97
|
+
phoneNumber: "(333) 333-33333",
|
|
98
|
+
emailAddress: "alex@mailserver.com",
|
|
99
|
+
gender: "Male"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "Amelia",
|
|
103
|
+
phoneNumber: "(444) 444-44444",
|
|
104
|
+
emailAddress: "amelia@mailserver.com",
|
|
105
|
+
gender: "Female"
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
});
|
|
65
109
|
</script>
|
|
66
110
|
|
|
67
111
|
<h3 id="customFields"><a href="#customFields" class="no-link">Custom Fields</a></h3>
|
|
68
|
-
<p>Use the
|
|
69
|
-
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"
|
|
70
|
-
<k-table id="
|
|
71
|
-
<script type="module">
|
|
72
|
-
document.getElementById('movies').setData([
|
|
73
|
-
{
|
|
74
|
-
name: 'Good Will Hunting',
|
|
75
|
-
year: 1997,
|
|
76
|
-
lead: 'Matt Damon',
|
|
77
|
-
budget: 10000000
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: 'A Few Good Men',
|
|
81
|
-
year: 1992,
|
|
82
|
-
lead: 'Tom Cruise',
|
|
83
|
-
prod: 'Castle Rock Entertainment'
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: 'The Silence of the Lambs',
|
|
87
|
-
year: 1991,
|
|
88
|
-
lead: 'Jodie Foster',
|
|
89
|
-
prequel: 'Red Dragon'
|
|
90
|
-
}
|
|
91
|
-
],[
|
|
92
|
-
{
|
|
93
|
-
name: "name",
|
|
94
|
-
label: "Name"
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
name: 'year',
|
|
98
|
-
label: 'Year'
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: 'prod',
|
|
102
|
-
label: 'Production Company'
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: 'lead',
|
|
106
|
-
label: 'Lead Actor / Actress'
|
|
107
|
-
}
|
|
108
|
-
]);
|
|
109
|
-
</script>
|
|
110
|
-
|
|
111
|
-
<h3 id="controls"><a href="#controls" class="no-link">Row Controls</a></h3>
|
|
112
|
-
<p>The table component can display controls before or after each row. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function. All controls are combined into a single <code><td></code> with each control being a button.</p>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
<k-table id="contacts2"></k-table>
|
|
112
|
+
<p>Use the <code>fields</code> property of the options object in <code>setData</code> to define custom fields. This is useful for hiding fields, reordering fields, changing the labels, formatting the output, or creating a calculated field.</p>
|
|
113
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"customFieldsExample"</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br /><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript"><br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'customFieldsExample'</span>).setData({<br /> records: contacts.slice(<span class="hljs-number">0</span>,<span class="hljs-number">10</span>),<br /> fields: [<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">"name"</span>,<br /> <span class="hljs-attr">label</span>: <span class="hljs-string">"Name"</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">'phoneNumber'</span>,<br /> <span class="hljs-attr">label</span>: <span class="hljs-string">'Phone Number'</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">'email'</span>,<br /> <span class="hljs-attr">label</span>: <span class="hljs-string">'Email Address'</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">'birthday'</span>,<br /> <span class="hljs-attr">label</span>: <span class="hljs-string">'Birthday'</span><br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">'age'</span>,<br /> <span class="hljs-attr">label</span>: <span class="hljs-string">'Age'</span>,<br /> <span class="hljs-attr">calculator</span>: <span class="hljs-function">(<span class="hljs-params">record</span>) =></span> {<br /> <span class="hljs-comment">// record.birthday is a string in the format "YYYY-MM-DD"</span><br /> <span class="hljs-keyword">const</span> today = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>();<br /> <span class="hljs-keyword">const</span> birthDate = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(record.birthday);<br /> <span class="hljs-keyword">let</span> age = today.getFullYear() - birthDate.getFullYear();<br /> <span class="hljs-keyword">const</span> monthDifference = today.getMonth() - birthDate.getMonth();<br /> <span class="hljs-keyword">if</span> (monthDifference < <span class="hljs-number">0</span> || (monthDifference === <span class="hljs-number">0</span> && today.getDate() < birthDate.getDate())) {<br /> age--;<br /> }<br /> <span class="hljs-keyword">return</span> age;<br /> }<br /> },<br /> {<br /> <span class="hljs-attr">name</span>: <span class="hljs-string">'gender'</span>,<br /> <span class="hljs-attr">label</span>: <span class="hljs-string">'Gender'</span>,<br /> <span class="hljs-attr">formatter</span>: <span class="hljs-function">(<span class="hljs-params">v</span>) =></span> v===<span class="hljs-string">'m'</span>?<span class="hljs-string">'Male'</span>:<span class="hljs-string">'Female'</span> <span class="hljs-comment">// original value is "m" or "f"</span><br /> }<br /> ]<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
114
|
+
<k-table id="customFieldsExample"></k-table>
|
|
116
115
|
<script type="module">
|
|
117
|
-
document.getElementById('
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
phoneNumber: "(111) 111-1111",
|
|
121
|
-
emailAddress: "dustin@mailserver.com",
|
|
122
|
-
gender: "Male"
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "Kayla",
|
|
126
|
-
phoneNumber: "(222) 222-2222",
|
|
127
|
-
emailAddress: "kayla@mailserver.com",
|
|
128
|
-
gender: "Female"
|
|
129
|
-
}
|
|
130
|
-
], false, {
|
|
131
|
-
before: [
|
|
116
|
+
document.getElementById('customFieldsExample').setData({
|
|
117
|
+
records: contacts.slice(0,10),
|
|
118
|
+
fields: [
|
|
132
119
|
{
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
}
|
|
136
|
-
],
|
|
137
|
-
after: [
|
|
120
|
+
name: "name",
|
|
121
|
+
label: "Name"
|
|
122
|
+
},
|
|
138
123
|
{
|
|
139
|
-
|
|
140
|
-
|
|
124
|
+
name: 'phoneNumber',
|
|
125
|
+
label: 'Phone Number'
|
|
141
126
|
},
|
|
142
127
|
{
|
|
143
|
-
|
|
128
|
+
name: 'email',
|
|
129
|
+
label: 'Email Address'
|
|
144
130
|
},
|
|
145
131
|
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
top: [
|
|
132
|
+
name: 'birthday',
|
|
133
|
+
label: 'Birthday'
|
|
134
|
+
},
|
|
150
135
|
{
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
136
|
+
name: 'age',
|
|
137
|
+
label: 'Age',
|
|
138
|
+
calculator: (record) => {
|
|
139
|
+
// record.birthday is a string in the format "YYYY-MM-DD"
|
|
140
|
+
const today = new Date();
|
|
141
|
+
const birthDate = new Date(record.birthday);
|
|
142
|
+
let age = today.getFullYear() - birthDate.getFullYear();
|
|
143
|
+
const monthDifference = today.getMonth() - birthDate.getMonth();
|
|
144
|
+
if (monthDifference < 0 || (monthDifference === 0 && today.getDate() < birthDate.getDate())) {
|
|
145
|
+
age--;
|
|
146
|
+
}
|
|
147
|
+
return age;
|
|
148
|
+
}
|
|
149
|
+
},
|
|
156
150
|
{
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
name: 'gender',
|
|
152
|
+
label: 'Gender',
|
|
153
|
+
formatter: (v) => v==='m'?'Male':'Female' // original value is "m" or "f"
|
|
159
154
|
}
|
|
160
155
|
]
|
|
161
156
|
});
|
|
162
157
|
</script>
|
|
163
158
|
|
|
164
|
-
<h3 id="
|
|
159
|
+
<h3 id="topBottomControls"><a href="#topBottomControls" class="no-link">Top and Bottom Controls</a></h3>
|
|
160
|
+
<p>The table component can display controls at the top and bottom of the table. These controls can be used for pagination, adding new records, or any other actions. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function.</p>
|
|
161
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"topBottomControlsExample"</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br /><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript"><br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'topBottomControlsExample'</span>).setData({<br /> records: contacts.slice(<span class="hljs-number">10</span>, <span class="hljs-number">20</span>),<br /> controls: {<br /> <span class="hljs-attr">top</span>: [<br /> {<br /> <span class="hljs-attr">icon</span>: <span class="hljs-string">'add'</span>,<br /> <span class="hljs-attr">action</span>: <span class="hljs-function">(<span class="hljs-params">table</span>) =></span> table.addRecord(generateContact())<br /> }<br /> ],<br /> <span class="hljs-attr">bottom</span>: [<br /> {<br /> <span class="hljs-attr">icon</span>: <span class="hljs-string">'refresh'</span>,<br /> <span class="hljs-attr">action</span>: <span class="hljs-function">(<span class="hljs-params">table</span>) =></span> {<br /> contacts = contacts.sort(<span class="hljs-function"><span class="hljs-params">()</span> =></span> <span class="hljs-built_in">Math</span>.random() - <span class="hljs-number">0.5</span>);<br /> table.setRecords(contacts.slice(<span class="hljs-number">0</span>, <span class="hljs-number">10</span>));<br /> }<br /> }<br /> ]<br /> }<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
162
|
+
<k-table id="topBottomControlsExample"></k-table>
|
|
163
|
+
<script type="module">
|
|
164
|
+
document.getElementById('topBottomControlsExample').setData({
|
|
165
|
+
records: contacts.slice(10, 20),
|
|
166
|
+
controls: {
|
|
167
|
+
top: [
|
|
168
|
+
{
|
|
169
|
+
icon: 'add',
|
|
170
|
+
action: (table) => table.addRecord(generateContact())
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
bottom: [
|
|
174
|
+
{
|
|
175
|
+
icon: 'refresh',
|
|
176
|
+
action: (table) => {
|
|
177
|
+
contacts = contacts.sort(() => Math.random() - 0.5);
|
|
178
|
+
table.setRecords(contacts.slice(0, 10));
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
</script>
|
|
185
|
+
|
|
186
|
+
<h3 id="rowControls"><a href="#rowControls" class="no-link">Row Controls</a></h3>
|
|
187
|
+
<p>The table component can display controls before or after each row. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function.</p>
|
|
188
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"rowControlsExample"</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br /><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript"><br /> <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'rowControlsExample'</span>).setData({<br /> records: contacts.slice(20, 25),<br /> controls: {<br /> <span class="hljs-attr">before</span>: [<br /> {<br /> <span class="hljs-attr">html</span>: <span class="hljs-string">'<span>Custom HTML</span> '</span><br /> },<br /> {<br /> <span class="hljs-attr">render</span>: <span class="hljs-function">(<span class="hljs-params">table, record</span>) =></span> {<br /> <span class="hljs-keyword">return</span> <span class="hljs-string">`<button onclick="alert('<span class="hljs-subst">${record.name}</span>')">Alert</button>`</span>;<br /> }<br /> }<br /> ],<br /> <span class="hljs-attr">after</span>: [<br /> {<br /> <span class="hljs-attr">icon</span>: <span class="hljs-string">'edit'</span>,<br /> <span class="hljs-attr">action</span>: <span class="hljs-function">(<span class="hljs-params">table, record, index</span>) =></span> {<br /> <span class="hljs-keyword">const</span> newName = prompt(<span class="hljs-string">"Enter a new name:"</span>, record.name);<br /> <span class="hljs-keyword">if</span> (newName) {<br /> record.name = newName;<br /> table.updateRecord(index, record);<br /> }<br /> }<br /> },<br /> {<br /> <span class="hljs-attr">icon</span>: <span class="hljs-string">'delete'</span>,<br /> <span class="hljs-attr">action</span>: <span class="hljs-function">(<span class="hljs-params">table, record, index</span>) =></span> {<br /> table.deleteRecord(index);<br /> }<br /> }<br /> ]<br /> }<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
189
|
+
<k-table id="rowControlsExample"></k-table>
|
|
190
|
+
<script type="module">
|
|
191
|
+
document.getElementById('rowControlsExample').setData({
|
|
192
|
+
records: contacts.slice(20, 25),
|
|
193
|
+
controls: {
|
|
194
|
+
before: [
|
|
195
|
+
{
|
|
196
|
+
html: '<span>Custom HTML</span> '
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
render: (table, record) => {
|
|
200
|
+
return `<button onclick="alert('${record.name}')">Alert</button>`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
after: [
|
|
205
|
+
{
|
|
206
|
+
icon: 'edit',
|
|
207
|
+
action: (table, record, index) => {
|
|
208
|
+
const newName = prompt("Enter a new name:", record.name);
|
|
209
|
+
if (newName) {
|
|
210
|
+
record.name = newName;
|
|
211
|
+
table.updateRecord(index, record);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
icon: 'delete',
|
|
217
|
+
action: (table, record, index) => {
|
|
218
|
+
table.deleteRecord(index);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
]
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
</script>
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
<h3 id="pagination"><a href="#pagination" class="no-link">Pagination</a></h3>
|
|
228
|
+
<p>The table component supports pagination. You can set the page size and navigate through pages using the provided methods.</p>
|
|
229
|
+
<p>There are some predefined controls that you can use directly from <code>Table.controls</code>. These controls include:</p>
|
|
230
|
+
<ul>
|
|
231
|
+
<li><code>prevPage</code>: A control to navigate to the previous page.</li>
|
|
232
|
+
<li><code>nextPage</code>: A control to navigate to the next page.</li>
|
|
233
|
+
<li><code>pageSelect</code>: A control to select a specific page.</li>
|
|
234
|
+
<li><code>spacer</code>: A spacer that flexes to separate the other controls to before it to the left, and after it to the far right.</li>
|
|
235
|
+
<li><code>pageSize</code>: A control to select the number of records per page.</li>
|
|
236
|
+
</ul>
|
|
237
|
+
<pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">k-table</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"paginationExample"</span>></span><span class="hljs-tag"></<span class="hljs-name">k-table</span>></span><br /><span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"module"</span>></span><span class="javascript"><br /> <span class="hljs-keyword">import</span> Table <span class="hljs-keyword">from</span> <span class="hljs-string">'../kempo/components/Table.js'</span>;<br /> <span class="hljs-keyword">const</span> $paginationExample = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">'paginationExample'</span>);<br /> $paginationExample.setData({<br /> <span class="hljs-attr">records</span>: contacts,<br /> <span class="hljs-attr">controls</span>: {<br /> <span class="hljs-attr">bottom</span>: [<br /> Table.controls.prevPage,<br /> Table.controls.nextPage,<br /> Table.controls.pageSelect,<br /> Table.controls.spacer,<br /> Table.controls.pageSize<br /> ]<br /> },<br /> <span class="hljs-attr">pageSize</span>: <span class="hljs-number">10</span>,<br /> <span class="hljs-attr">pageSizeOptions</span>: [<span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-number">20</span>]<br /> });<br /></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span></code></pre>
|
|
238
|
+
<k-table id="paginationExample"></k-table>
|
|
239
|
+
<script type="module">
|
|
240
|
+
import Table from '../kempo/components/Table.js';
|
|
241
|
+
const $paginationExample = document.getElementById('paginationExample');
|
|
242
|
+
$paginationExample.setData({
|
|
243
|
+
records: contacts,
|
|
244
|
+
controls: {
|
|
245
|
+
bottom: [
|
|
246
|
+
Table.controls.prevPage,
|
|
247
|
+
Table.controls.nextPage,
|
|
248
|
+
Table.controls.pageSelect,
|
|
249
|
+
Table.controls.spacer,
|
|
250
|
+
Table.controls.pageSize
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
pageSize: 10,
|
|
254
|
+
pageSizeOptions: [5, 10, 20]
|
|
255
|
+
});
|
|
256
|
+
</script>
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
<h2>JavaScript Reference</h2>
|
|
260
|
+
<h3 id="constructor"><a href="#constructor" class="no-link">Constructor</a></h3>
|
|
165
261
|
<h6>Extends <a href="./component.html">Component</a></h6>
|
|
166
262
|
<h5>
|
|
167
263
|
<code>new Table()</code><br />
|
|
168
|
-
<code>new Table(<i><Array>object</i>
|
|
169
|
-
<code>new Table(<i><Array>object</i> records, <i><Array>object</i> fields)</code>
|
|
264
|
+
<code>new Table(<i><Array>object</i> options)</code>
|
|
170
265
|
</h5>
|
|
171
266
|
|
|
172
|
-
<h4>
|
|
173
|
-
<h5><code><i
|
|
174
|
-
<p>An
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
267
|
+
<h4>Parameters</h4>
|
|
268
|
+
<h5><code><i>object</i> options</code></h5>
|
|
269
|
+
<p>An object containing the initial configuration for the table. The options object can contain the following properties:</p>
|
|
270
|
+
<ul>
|
|
271
|
+
<li><code>records</code>: An array of objects representing the records to be shown in the table.</li>
|
|
272
|
+
<li><code>fields</code>: An array of objects representing the fields of the table, each object must contain a <code>name</code> and a <code>label</code>. If this property is omitted, the fields will be automatically generated from the first 100 records.</li>
|
|
273
|
+
<li><code>controls</code>: An object containing optional arrays of controls to be displayed before and/or after each row. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function. The <code>controls</code> object can contain the optional <code>before</code> and <code>after</code> properties, which are arrays of objects containing the <code>icon</code> (string), <code>action</code> (function), <code>html</code> (string), and <code>render</code> (function). If <code>html</code> is present, it overrides the icon and is rendered in its place. If <code>render</code> is present, it is used to render the control.</li>
|
|
274
|
+
<li><code>pageSize</code>: The number of records to display per page.</li>
|
|
275
|
+
<li><code>pageSizeOptions</code>: An array of numbers representing the available page size options.</li>
|
|
276
|
+
</ul>
|
|
178
277
|
|
|
179
278
|
<h3 id="requirements"><a href="#requirements" class="no-link">Requirements</a></h3>
|
|
180
279
|
<ul>
|
|
@@ -183,49 +282,58 @@
|
|
|
183
282
|
|
|
184
283
|
<h3 id="properties"><a href="#properties" class="no-link">Properties</a></h3>
|
|
185
284
|
<h5><code>fields<i>: <Array>object</i></code></h5>
|
|
186
|
-
<p>An array of objects containing a <code>name</code> and <code>
|
|
285
|
+
<p>An array of objects containing a <code>name</code>, <code>label</code>, <code>renderer</code> function, and <code>calculator</code> function that represent the fields to be displayed. The <code>renderer</code> function can be used to customize the display of the field, and the <code>calculator</code> function can be used to calculate the value of the field based on other fields in the record.</p>
|
|
187
286
|
|
|
188
287
|
<h5><code>records<i>: <Array>object</i></code></h5>
|
|
189
288
|
<p>An array of objects containing the data that will be the records of the table, where the object keys match the field names.</p>
|
|
190
289
|
|
|
191
|
-
<h5><code
|
|
192
|
-
<p>An object containing optional arrays of controls to be displayed before and/or after each row. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function. The <code>controls</code> object can contain the optional <code>before</code> and <code>
|
|
193
|
-
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
],
|
|
201
|
-
after: [
|
|
202
|
-
{
|
|
203
|
-
icon: 'delete',
|
|
204
|
-
action: (record, table) => console.log('Delete', record, table)
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
html: '<span>Custom HTML</span>'
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
render: (record, table) => `<button onclick="console.log('Rendered Action', ${record}, ${table})">Rendered Button</button>`
|
|
211
|
-
}
|
|
212
|
-
]
|
|
213
|
-
}</code></pre>
|
|
290
|
+
<h5><code>controls<i>: object</i></code></h5>
|
|
291
|
+
<p>An object containing optional arrays of controls to be displayed before and/or after each row, or above and below the table. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function. The <code>controls</code> object can contain the optional <code>before</code>, <code>after</code>, <code>top</code>, and <code>bottom</code> properties, which are arrays of objects containing the <code>icon</code> (string), <code>action</code> (function), <code>html</code> (string), and <code>render</code> (function). If <code>html</code> is present, it overrides the icon and is rendered in its place. If <code>render</code> is present, it is used to render the control. Controls in the <code>before</code> or <code>after</code> arrays receive the record and index as the 2nd and 3rd parameters in the action function and the renderer function.</p>
|
|
292
|
+
|
|
293
|
+
<h5><code>pageSize<i>: number</i></code></h5>
|
|
294
|
+
<p>The number of records to display per page.</p>
|
|
295
|
+
|
|
296
|
+
<h5><code>pageSizeOptions<i>: <Array>number</i></code></h5>
|
|
297
|
+
<p>An array of numbers representing the available page size options.</p>
|
|
214
298
|
|
|
215
299
|
<h3 id="methods"><a href="#methods" class="no-link">Methods</a></h3>
|
|
216
300
|
|
|
217
|
-
<h5><code>setData(<i
|
|
218
|
-
<p>Sets the records and the fields and renders the table
|
|
301
|
+
<h5><code>setData(<i>object</i> options)<i>: undefined</i></code></h5>
|
|
302
|
+
<p>Sets the records and the fields and renders the table. If no fields are provided, they will be automatically generated from the keys of the first 100 records. The options object can contain the following properties:</p>
|
|
303
|
+
<ul>
|
|
304
|
+
<li><code>records</code>: An array of objects representing the records to be shown in the table.</li>
|
|
305
|
+
<li><code>fields</code>: An array of objects representing the fields of the table, each object must contain a <code>name</code> and a <code>label</code>. If this property is omitted, the fields will be automatically generated from the first 100 records.</li>
|
|
306
|
+
<li><code>controls</code>: An object containing optional arrays of controls to be displayed before and/or after each row. Each control is defined by an icon name, a callback function, and optionally an HTML string or a render function. The <code>controls</code> object can contain the optional <code>before</code> and <code>after</code> properties, which are arrays of objects containing the <code>icon</code> (string), <code>action</code> (function), <code>html</code> (string), and <code>render</code> (function). If <code>html</code> is present, it overrides the icon and is rendered in its place. If <code>render</code> is present, it is used to render the control.</li>
|
|
307
|
+
<li><code>pageSize</code>: The number of records to display per page.</li>
|
|
308
|
+
<li><code>pageSizeOptions</code>: An array of numbers representing the available page size options.</li>
|
|
309
|
+
</ul>
|
|
219
310
|
|
|
220
311
|
<h5><code>renderFields()<i>: undefined</i></code></h5>
|
|
221
|
-
<p>Renders the fields as table headers
|
|
312
|
+
<p>Renders the fields as table headers. This is called automatically by setData, you should never have to call it.</p>
|
|
222
313
|
|
|
223
314
|
<h5><code>renderRecords()<i>: undefined</i></code></h5>
|
|
224
|
-
<p>Renders the records as table
|
|
315
|
+
<p>Renders the records as table rows. This is called automatically by setData, you should never have to call it.</p>
|
|
316
|
+
|
|
317
|
+
<h5><code>addRecord(<i>object</i> record)<i>: undefined</i></code></h5>
|
|
318
|
+
<p>Adds a new record to the table.</p>
|
|
319
|
+
|
|
320
|
+
<h5><code>updateRecord(<i>number</i> index, <i>object</i> record)<i>: undefined</i></code></h5>
|
|
321
|
+
<p>Updates an existing record at the specified index.</p>
|
|
322
|
+
|
|
323
|
+
<h5><code>deleteRecord(<i>number</i> index)<i>: undefined</i></code></h5>
|
|
324
|
+
<p>Deletes a record at the specified index.</p>
|
|
325
|
+
|
|
326
|
+
<h5><code>setRecords(<i>Array</i> records)<i>: undefined</i></code></h5>
|
|
327
|
+
<p>Sets the records for the table and re-renders the rows.</p>
|
|
328
|
+
|
|
329
|
+
<h5><code>setPageSize(<i>number</i> pageSize)<i>: undefined</i></code></h5>
|
|
330
|
+
<p>Sets the number of records to display per page.</p>
|
|
331
|
+
|
|
332
|
+
<h5><code>goToPage(<i>number</i> pageNumber)<i>: undefined</i></code></h5>
|
|
333
|
+
<p>Navigates to the specified page number.</p>
|
|
225
334
|
|
|
226
335
|
</main>
|
|
227
336
|
<div style="height: 33.333vh"></div>
|
|
228
337
|
<script type="module" src="../kempo/components/Import.js"></script>
|
|
229
|
-
|
|
230
338
|
</body>
|
|
231
339
|
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z"/></svg>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import Component from"./Component.js";import Icon from"./Icon.js";import{toTitleCase}from"../utils/string.js";export default class Table extends Component{
|
|
1
|
+
import Component from"./Component.js";import Icon from"./Icon.js";import{toTitleCase}from"../utils/string.js";import{onEvent,dispatchEvent}from"../utils/element.js";export default class Table extends Component{static controls={prevPage:{render:e=>{const t=document.createElement("button");return t.classList.add("pq"),t.appendChild(new Icon("chevron-left")),onEvent(t,"click",(()=>e.prevPage())),onEvent(e,"pageChange",(()=>{t.disabled=1===e.getCurrentPage()})),t}},nextPage:{render:e=>{const t=document.createElement("button");return t.classList.add("pq"),t.appendChild(new Icon("chevron-right")),onEvent(t,"click",(()=>e.nextPage())),onEvent(e,"pageChange",(()=>{t.disabled=e.getCurrentPage()===e.getTotalPages()})),t}},pageSelect:{render:e=>new PageSelect(e)},pageSize:{render:e=>new PageSize(e)},spacer:{html:'<div class="flex"></div>'}};constructor({records:e=[],fields:t=[],controls:n={before:[],after:[],top:[],bottom:[]},pageSize:s=100,pageSizeOptions:r=[10,25,50,100,500],currentPage:a=1}={}){super(),this.registerProps({fields:t,records:e,controls:n,pageSize:s,pageSizeOptions:r,currentPage:a})}async render(e){return!!await super.render(e)&&(this.setData({records:this.records,fields:this.fields,controls:this.controls,pageSize:this.pageSize,pageSizeOptions:this.pageSizeOptions,currentPage:this.currentPage}),!0)}renderFields(){if(!this.rendered)return;const e=this.controls?.before?.length?"<th></th>":"",t=this.controls?.after?.length?"<th></th>":"";this.shadowRoot.getElementById("fields").innerHTML=`${e}${this.fields.map((({label:e})=>`<th>${e}</th>`)).join("")}${t}`}renderRecords(){if(!this.rendered)return;const e=this.shadowRoot.getElementById("records");e.innerHTML="";const t=(this.currentPage-1)*this.pageSize,n=t+this.pageSize;this.records.slice(t,n).forEach(((s,r)=>{const a=document.createElement("tr");this.controls?.before?.length&&a.appendChild(this.renderRowControls(this.controls.before,s,t+r)),this.fields.forEach((({name:e,formatter:r,calculator:o})=>{const i=document.createElement("td");if(null===s)i.innerHTML="Loading...",dispatchEvent(this,"fetchRecords",{start:t,end:n});else{let t;o?t=o(s,this):(t=s[e]||"",Array.isArray(t)?t=r?t.map(r).join(", "):t.join(", "):r&&(t=r(t))),i.innerHTML=t}a.appendChild(i)})),this.controls?.after?.length&&a.appendChild(this.renderRowControls(this.controls.after,s,t+r)),e.appendChild(a)}))}renderRowControls(e=[],t,n){if(!this.rendered)return;const s=document.createElement("td");return e.forEach((({html:e,icon:r,action:a,render:o})=>{if(e)s.appendChild(document.createRange().createContextualFragment(e));else if(o&&"function"==typeof o){const e=o(this,t,n);e instanceof HTMLElement?s.appendChild(e):"string"==typeof e&&s.appendChild(document.createRange().createContextualFragment(e))}else if(r){const e=document.createElement("button");e.classList.add("pq"),e.appendChild(new Icon(r)),a&&onEvent(e,"click",(()=>a(this,t,n))),s.appendChild(e)}})),s}renderControls(e=[],t){if(!this.rendered)return;const n=this.shadowRoot.getElementById(t);n.innerHTML="",e.forEach((({html:e,icon:t,action:s,render:r})=>{if(e)n.appendChild(document.createRange().createContextualFragment(e));else if(r&&"function"==typeof r){const e=r(this);e instanceof HTMLElement?n.appendChild(e):"string"==typeof e&&n.appendChild(document.createRange().createContextualFragment(e))}else if(t){const e=document.createElement("button");e.classList.add("pq"),e.appendChild(new Icon(t)),s&&onEvent(e,"click",(()=>s(this))),n.appendChild(e)}}))}getCurrentPage(){return this.currentPage}getTotalPages(){return Math.ceil(this.records.length/this.pageSize)}setPage(e){e<1||e>this.getTotalPages()||(this.currentPage=e,this.renderRecords(),dispatchEvent(this,"pageChange"))}nextPage(){this.currentPage<this.getTotalPages()&&this.setPage(this.currentPage+1)}prevPage(){this.currentPage>1&&this.setPage(this.currentPage-1)}setPageSize(e){this.pageSize=e,this.currentPage=1,this.renderRecords(),dispatchEvent(this,"pageSizeChange")}setData({records:e=!1,fields:t=!1,controls:n=!1,pageSize:s=!1,pageSizeOptions:r=!1,currentPage:a=!1}={}){let o=!1,i=!1;e&&(this.records=e,this.fields=t||Table.extractFieldsFromRecords(this.records),o=!0),s&&(this.pageSize=s,o=!0),r&&(this.pageSizeOptions=r),n&&(this.controls=n,i=!0),a&&(this.currentPage=a,o=!0),o&&(this.renderFields(),this.renderRecords()),i&&(this.renderControls(this.controls.top,"top"),this.renderControls(this.controls.bottom,"bottom"))}setRecords(e){this.records=e,this.currentPage=1,this.renderRecords(),dispatchEvent(this,"recordsSet",{records:e})}setupFetchRecords(e,t){const n=this.records.length;n<e&&(this.records.length=e,this.records.fill(null,n)),onEvent(this,"fetchRecords",(async e=>{const{start:n,end:s}=e.detail,r=await t(n,s-n);this.records.splice(n,r.length,...r),this.renderRecords()}))}addRecord(e){this.records.push(e),this.renderRecords(),dispatchEvent(this,"recordAdded",{record:e})}updateRecord(e,t){this.records[e]=t;const n=(this.currentPage-1)*this.pageSize,s=n+this.pageSize;e>=n&&e<s&&this.renderRecords()}deleteRecord(e){e>=0&&e<this.records.length&&(this.records.splice(e,1),this.renderRecords(),dispatchEvent(this,"recordDeleted",{index:e}))}get shadowTemplate(){return`\n ${super.shadowTemplate}\n <div class="responsive-table">\n <div id="top"></div>\n <table class="b0">\n <thead id="fields"></thead>\n <tbody id="records"></tbody>\n </table>\n <div id="bottom"></div>\n </div>\n `}get shadowStyles(){return`\n ${super.shadowStyles}\n :host {\n display: block;\n margin-bottom: var(--spacer);\n border: 1px solid var(--c_border);\n border-radius: var(--radius);\n }\n #top, #bottom {\n display: flex;\n }\n th {\n border-top: none;\n }\n tr:last-child td:first-child {\n border-bottom-left-radius: 0;\n }\n tr:last-child td:last-child {\n border-bottom-right-radius: 0;\n }\n tr:last-child td {\n border-bottom: none;\n }\n th:first-child, td:first-child {\n border-left: none;\n }\n th:last-child, td:last-child {\n border-right: none;\n }\n #top:not(:empty) {\n border-bottom: 1px solid var(--c_border);\n }\n #bottom:not(:empty) {\n border-top: 1px solid var(--c_border);\n }\n `}static extractFieldsFromRecords(e,t=100){const n=new Set;return e.slice(0,t).forEach((e=>{Object.keys(e).forEach((e=>n.add(e)))})),[...n].map((e=>({name:e,label:toTitleCase(e)})))}}window.customElements.define("k-table",Table);class PageSelect extends Component{constructor(e){super(),this.table=e,this.classList.add("mxq")}connectedCallback(){this.render()}async render(){return!!await super.render()&&(this.updateOptions(),!0)}updateOptions(){const e=this.shadowRoot.getElementById("pageSelect");e.innerHTML="";for(let t=1;t<=this.table.getTotalPages();t++){const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)}e.value=this.table.getCurrentPage();this.shadowRoot.getElementById("totalPages").textContent=this.table.getTotalPages(),onEvent(e,"change",(()=>{this.table.setPage(parseInt(e.value))})),onEvent(this.table,"pageSizeChange",(()=>{this.updateOptions()})),onEvent(this.table,"pageChange",(()=>{e.value=this.table.getCurrentPage()}))}get shadowTemplate(){return'\n <select id="pageSelect" class="mxq"></select>\n <label> out of <span id="totalPages"></span></label>\n '}get shadowStyles(){return"\n :host {\n display: inline-flex;\n width: max-content;\n align-items: baseline;\n }\n #pageSelect, label {\n display: inline;\n }\n label {\n white-space: nowrap;\n }\n "}}window.customElements.define("k-table-page-select",PageSelect);class PageSize extends Component{constructor(e){super(),this.table=e,this.classList.add("mxq")}connectedCallback(){this.render()}async render(){if(await super.render()){const e=this.shadowRoot.getElementById("pageSizeSelect");return this.table.pageSizeOptions.forEach((t=>{const n=document.createElement("option");n.value=t,n.textContent=t,e.appendChild(n)})),e.value=this.table.pageSize,onEvent(e,"change",(()=>{this.table.setPageSize(parseInt(e.value))})),onEvent(this.table,"pageSizeChange",(()=>{e.value=this.table.pageSize})),!0}return!1}get shadowTemplate(){return'\n <label for="pageSizeSelect">Page Size: </label>\n <select id="pageSizeSelect" class="mxq"></select>\n '}get shadowStyles(){return"\n :host {\n display: inline-flex;\n width: max-content;\n align-items: baseline;\n }\n label {\n white-space: nowrap;\n }\n "}}window.customElements.define("k-table-page-size",PageSize);
|
package/icons/add.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M440-440H200v-80h240v-240h80v240h240v80H520v240h-80v-240Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960"><path fill="currentColor" d="M480-160q-134 0-227-93t-93-227q0-134 93-227t227-93q69 0 132 28.5T720-690v-110h80v280H520v-80h168q-32-56-87.5-88T480-720q-100 0-170 70t-70 170q0 100 70 170t170 70q77 0 139-44t87-116h84q-28 106-114 173t-196 67Z"/></svg>
|
package/package.json
CHANGED
package/src/components/Table.js
CHANGED
|
@@ -1,68 +1,147 @@
|
|
|
1
1
|
import Component from './Component.js';
|
|
2
|
-
import Icon from './Icon.js';
|
|
2
|
+
import Icon from './Icon.js';
|
|
3
3
|
import { toTitleCase } from '../utils/string.js';
|
|
4
|
+
import { onEvent, dispatchEvent } from '../utils/element.js';
|
|
4
5
|
|
|
5
6
|
export default class Table extends Component {
|
|
6
|
-
|
|
7
|
+
static controls = {
|
|
8
|
+
prevPage: {
|
|
9
|
+
render: (table) => {
|
|
10
|
+
const $button = document.createElement('button');
|
|
11
|
+
$button.classList.add('pq');
|
|
12
|
+
$button.appendChild(new Icon('chevron-left'));
|
|
13
|
+
onEvent($button, 'click', () => table.prevPage());
|
|
14
|
+
|
|
15
|
+
// Disable button if on the first page
|
|
16
|
+
onEvent(table, 'pageChange', () => {
|
|
17
|
+
$button.disabled = table.getCurrentPage() === 1;
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
return $button;
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
nextPage: {
|
|
24
|
+
render: (table) => {
|
|
25
|
+
const $button = document.createElement('button');
|
|
26
|
+
$button.classList.add('pq');
|
|
27
|
+
$button.appendChild(new Icon('chevron-right'));
|
|
28
|
+
onEvent($button, 'click', () => table.nextPage());
|
|
29
|
+
|
|
30
|
+
// Disable button if on the last page
|
|
31
|
+
onEvent(table, 'pageChange', () => {
|
|
32
|
+
$button.disabled = table.getCurrentPage() === table.getTotalPages();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return $button;
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
pageSelect: {
|
|
39
|
+
render: (table) => new PageSelect(table)
|
|
40
|
+
},
|
|
41
|
+
pageSize: {
|
|
42
|
+
render: (table) => new PageSize(table)
|
|
43
|
+
},
|
|
44
|
+
spacer: {
|
|
45
|
+
html: '<div class="flex"></div>'
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
constructor({
|
|
50
|
+
records = [],
|
|
51
|
+
fields = [],
|
|
52
|
+
controls = { before: [], after: [], top: [], bottom: [] },
|
|
53
|
+
pageSize = 100,
|
|
54
|
+
pageSizeOptions = [10, 25, 50, 100, 500],
|
|
55
|
+
currentPage = 1,
|
|
56
|
+
} = {}) {
|
|
7
57
|
super();
|
|
8
58
|
|
|
9
59
|
this.registerProps({
|
|
10
|
-
fields:
|
|
11
|
-
records:
|
|
12
|
-
controls:
|
|
60
|
+
fields: fields,
|
|
61
|
+
records: records,
|
|
62
|
+
controls: controls,
|
|
63
|
+
pageSize,
|
|
64
|
+
pageSizeOptions,
|
|
65
|
+
currentPage,
|
|
13
66
|
});
|
|
14
67
|
|
|
15
68
|
/* Init */
|
|
16
|
-
|
|
17
|
-
this.setData(data, fields, controls);
|
|
18
|
-
}
|
|
69
|
+
//
|
|
19
70
|
}
|
|
20
71
|
async render(force){
|
|
21
72
|
if(await super.render(force)){
|
|
22
|
-
this.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
73
|
+
this.setData({
|
|
74
|
+
records: this.records,
|
|
75
|
+
fields: this.fields,
|
|
76
|
+
controls: this.controls,
|
|
77
|
+
pageSize: this.pageSize,
|
|
78
|
+
pageSizeOptions: this.pageSizeOptions,
|
|
79
|
+
currentPage: this.currentPage
|
|
80
|
+
});
|
|
26
81
|
return true;
|
|
27
82
|
}
|
|
28
83
|
return false;
|
|
29
84
|
}
|
|
30
85
|
renderFields(){
|
|
86
|
+
if(!this.rendered) return;
|
|
31
87
|
const beforeControls = this.controls?.before?.length ? '<th></th>' : '';
|
|
32
88
|
const afterControls = this.controls?.after?.length ? '<th></th>' : '';
|
|
33
89
|
this.shadowRoot.getElementById('fields').innerHTML = `${beforeControls}${this.fields.map(({label})=>`<th>${label}</th>`).join('')}${afterControls}`;
|
|
34
90
|
}
|
|
35
91
|
renderRecords(){
|
|
92
|
+
if(!this.rendered) return;
|
|
36
93
|
const $records = this.shadowRoot.getElementById('records');
|
|
37
94
|
$records.innerHTML = '';
|
|
38
|
-
this.
|
|
95
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
96
|
+
const end = start + this.pageSize;
|
|
97
|
+
const paginatedRecords = this.records.slice(start, end);
|
|
98
|
+
paginatedRecords.forEach((record, index) => {
|
|
39
99
|
const $tr = document.createElement('tr');
|
|
40
100
|
if(this.controls?.before?.length){
|
|
41
|
-
$tr.appendChild(this.renderRowControls(this.controls.before, record));
|
|
101
|
+
$tr.appendChild(this.renderRowControls(this.controls.before, record, start + index));
|
|
42
102
|
}
|
|
43
|
-
this.fields.forEach(({name}) => {
|
|
103
|
+
this.fields.forEach(({name, formatter, calculator}) => {
|
|
44
104
|
const $td = document.createElement('td');
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
105
|
+
if (record === null) {
|
|
106
|
+
$td.innerHTML = 'Loading...';
|
|
107
|
+
dispatchEvent(this, 'fetchRecords', { start, end });
|
|
108
|
+
} else {
|
|
109
|
+
let value;
|
|
110
|
+
if(calculator){
|
|
111
|
+
value = calculator(record, this);
|
|
112
|
+
} else {
|
|
113
|
+
value = record[name] || '';
|
|
114
|
+
if (Array.isArray(value)) {
|
|
115
|
+
if(formatter){
|
|
116
|
+
value = value.map(formatter).join(', ');
|
|
117
|
+
} else {
|
|
118
|
+
value = value.join(', ');
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
if(formatter){
|
|
122
|
+
value = formatter(value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
$td.innerHTML = value;
|
|
48
127
|
}
|
|
49
|
-
$td.innerHTML = value;
|
|
50
128
|
$tr.appendChild($td);
|
|
51
129
|
});
|
|
52
130
|
if(this.controls?.after?.length){
|
|
53
|
-
$tr.appendChild(this.renderRowControls(this.controls.after, record));
|
|
131
|
+
$tr.appendChild(this.renderRowControls(this.controls.after, record, start + index));
|
|
54
132
|
}
|
|
55
133
|
$records.appendChild($tr);
|
|
56
134
|
});
|
|
57
135
|
}
|
|
58
136
|
|
|
59
|
-
renderRowControls(controls = [], record) {
|
|
137
|
+
renderRowControls(controls = [], record, index) {
|
|
138
|
+
if(!this.rendered) return;
|
|
60
139
|
const $td = document.createElement('td');
|
|
61
140
|
controls.forEach(({ html, icon, action, render }) => {
|
|
62
141
|
if(html){
|
|
63
142
|
$td.appendChild(document.createRange().createContextualFragment(html));
|
|
64
143
|
} else if(render && typeof(render) === 'function'){
|
|
65
|
-
const rendered = render(this, record);
|
|
144
|
+
const rendered = render(this, record, index);
|
|
66
145
|
if(rendered instanceof HTMLElement){
|
|
67
146
|
$td.appendChild(rendered);
|
|
68
147
|
} else if(typeof(rendered) === 'string'){
|
|
@@ -70,9 +149,10 @@ export default class Table extends Component {
|
|
|
70
149
|
}
|
|
71
150
|
} else if(icon){
|
|
72
151
|
const $button = document.createElement('button');
|
|
152
|
+
$button.classList.add('pq');
|
|
73
153
|
$button.appendChild(new Icon(icon));
|
|
74
154
|
if (action) {
|
|
75
|
-
$button
|
|
155
|
+
onEvent($button, 'click', () => action(this, record, index));
|
|
76
156
|
}
|
|
77
157
|
$td.appendChild($button);
|
|
78
158
|
}
|
|
@@ -81,7 +161,9 @@ export default class Table extends Component {
|
|
|
81
161
|
}
|
|
82
162
|
|
|
83
163
|
renderControls(controls = [], position) {
|
|
84
|
-
|
|
164
|
+
if(!this.rendered) return;
|
|
165
|
+
const $container = this.shadowRoot.getElementById(position);
|
|
166
|
+
$container.innerHTML = '';
|
|
85
167
|
controls.forEach(({ html, icon, action, render }) => {
|
|
86
168
|
if(html){
|
|
87
169
|
$container.appendChild(document.createRange().createContextualFragment(html));
|
|
@@ -94,25 +176,135 @@ export default class Table extends Component {
|
|
|
94
176
|
}
|
|
95
177
|
} else if(icon){
|
|
96
178
|
const $button = document.createElement('button');
|
|
179
|
+
$button.classList.add('pq');
|
|
97
180
|
$button.appendChild(new Icon(icon));
|
|
98
181
|
if (action) {
|
|
99
|
-
$button
|
|
182
|
+
onEvent($button, 'click', () => action(this));
|
|
100
183
|
}
|
|
101
184
|
$container.appendChild($button);
|
|
102
185
|
}
|
|
103
186
|
});
|
|
104
|
-
this.shadowRoot.getElementById(position).innerHTML = '';
|
|
105
|
-
this.shadowRoot.getElementById(position).appendChild($container);
|
|
106
187
|
}
|
|
107
188
|
|
|
108
|
-
|
|
189
|
+
getCurrentPage() {
|
|
190
|
+
return this.currentPage;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
getTotalPages() {
|
|
194
|
+
return Math.ceil(this.records.length / this.pageSize);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
setPage(page) {
|
|
198
|
+
if (page < 1 || page > this.getTotalPages()) return;
|
|
199
|
+
this.currentPage = page;
|
|
200
|
+
this.renderRecords();
|
|
201
|
+
// Trigger page change event
|
|
202
|
+
dispatchEvent(this, 'pageChange');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
nextPage() {
|
|
206
|
+
if (this.currentPage < this.getTotalPages()) {
|
|
207
|
+
this.setPage(this.currentPage + 1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
prevPage() {
|
|
212
|
+
if (this.currentPage > 1) {
|
|
213
|
+
this.setPage(this.currentPage - 1);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setPageSize(pageSize) {
|
|
218
|
+
this.pageSize = pageSize;
|
|
219
|
+
this.currentPage = 1;
|
|
220
|
+
this.renderRecords();
|
|
221
|
+
// Trigger page size change event
|
|
222
|
+
dispatchEvent(this, 'pageSizeChange');
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
setData({
|
|
226
|
+
records = false,
|
|
227
|
+
fields = false,
|
|
228
|
+
controls = false,
|
|
229
|
+
pageSize = false,
|
|
230
|
+
pageSizeOptions = false,
|
|
231
|
+
currentPage = false
|
|
232
|
+
} = {}) {
|
|
233
|
+
let rerender = false;
|
|
234
|
+
let rerenderControls = false;
|
|
235
|
+
if(records){
|
|
236
|
+
this.records = records;
|
|
237
|
+
this.fields = fields || Table.extractFieldsFromRecords(this.records);
|
|
238
|
+
rerender = true;
|
|
239
|
+
}
|
|
240
|
+
if (pageSize) {
|
|
241
|
+
this.pageSize = pageSize;
|
|
242
|
+
rerender = true;
|
|
243
|
+
}
|
|
244
|
+
if (pageSizeOptions) {
|
|
245
|
+
this.pageSizeOptions = pageSizeOptions;
|
|
246
|
+
}
|
|
247
|
+
if (controls) {
|
|
248
|
+
this.controls = controls;
|
|
249
|
+
rerenderControls = true;
|
|
250
|
+
}
|
|
251
|
+
if(currentPage){
|
|
252
|
+
this.currentPage = currentPage;
|
|
253
|
+
rerender = true;
|
|
254
|
+
}
|
|
255
|
+
if(rerender){
|
|
256
|
+
this.renderFields();
|
|
257
|
+
this.renderRecords();
|
|
258
|
+
}
|
|
259
|
+
if(rerenderControls){
|
|
260
|
+
this.renderControls(this.controls.top, 'top');
|
|
261
|
+
this.renderControls(this.controls.bottom, 'bottom');
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
setRecords(records) {
|
|
109
266
|
this.records = records;
|
|
110
|
-
this.
|
|
111
|
-
this.controls = controls;
|
|
112
|
-
this.renderControls(this.controls.top, 'top');
|
|
113
|
-
this.renderFields();
|
|
267
|
+
this.currentPage = 1;
|
|
114
268
|
this.renderRecords();
|
|
115
|
-
|
|
269
|
+
dispatchEvent(this, 'recordsSet', { records });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
setupFetchRecords(totalRecords, callback) {
|
|
273
|
+
const previousLength = this.records.length;
|
|
274
|
+
if (previousLength < totalRecords) {
|
|
275
|
+
this.records.length = totalRecords;
|
|
276
|
+
this.records.fill(null, previousLength);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
onEvent(this, 'fetchRecords', async (event) => {
|
|
280
|
+
const { start, end } = event.detail;
|
|
281
|
+
const records = await callback(start, end - start);
|
|
282
|
+
this.records.splice(start, records.length, ...records);
|
|
283
|
+
this.renderRecords();
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
addRecord(record) {
|
|
288
|
+
this.records.push(record);
|
|
289
|
+
this.renderRecords();
|
|
290
|
+
dispatchEvent(this, 'recordAdded', { record });
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
updateRecord(index, newRecord) {
|
|
294
|
+
this.records[index] = newRecord;
|
|
295
|
+
const start = (this.currentPage - 1) * this.pageSize;
|
|
296
|
+
const end = start + this.pageSize;
|
|
297
|
+
if (index >= start && index < end) {
|
|
298
|
+
this.renderRecords();
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
deleteRecord(index) {
|
|
303
|
+
if (index >= 0 && index < this.records.length) {
|
|
304
|
+
this.records.splice(index, 1);
|
|
305
|
+
this.renderRecords();
|
|
306
|
+
dispatchEvent(this, 'recordDeleted', { index });
|
|
307
|
+
}
|
|
116
308
|
}
|
|
117
309
|
|
|
118
310
|
get shadowTemplate(){
|
|
@@ -120,7 +312,7 @@ export default class Table extends Component {
|
|
|
120
312
|
${super.shadowTemplate}
|
|
121
313
|
<div class="responsive-table">
|
|
122
314
|
<div id="top"></div>
|
|
123
|
-
<table>
|
|
315
|
+
<table class="b0">
|
|
124
316
|
<thead id="fields"></thead>
|
|
125
317
|
<tbody id="records"></tbody>
|
|
126
318
|
</table>
|
|
@@ -134,6 +326,35 @@ export default class Table extends Component {
|
|
|
134
326
|
:host {
|
|
135
327
|
display: block;
|
|
136
328
|
margin-bottom: var(--spacer);
|
|
329
|
+
border: 1px solid var(--c_border);
|
|
330
|
+
border-radius: var(--radius);
|
|
331
|
+
}
|
|
332
|
+
#top, #bottom {
|
|
333
|
+
display: flex;
|
|
334
|
+
}
|
|
335
|
+
th {
|
|
336
|
+
border-top: none;
|
|
337
|
+
}
|
|
338
|
+
tr:last-child td:first-child {
|
|
339
|
+
border-bottom-left-radius: 0;
|
|
340
|
+
}
|
|
341
|
+
tr:last-child td:last-child {
|
|
342
|
+
border-bottom-right-radius: 0;
|
|
343
|
+
}
|
|
344
|
+
tr:last-child td {
|
|
345
|
+
border-bottom: none;
|
|
346
|
+
}
|
|
347
|
+
th:first-child, td:first-child {
|
|
348
|
+
border-left: none;
|
|
349
|
+
}
|
|
350
|
+
th:last-child, td:last-child {
|
|
351
|
+
border-right: none;
|
|
352
|
+
}
|
|
353
|
+
#top:not(:empty) {
|
|
354
|
+
border-bottom: 1px solid var(--c_border);
|
|
355
|
+
}
|
|
356
|
+
#bottom:not(:empty) {
|
|
357
|
+
border-top: 1px solid var(--c_border);
|
|
137
358
|
}
|
|
138
359
|
`;
|
|
139
360
|
}
|
|
@@ -147,3 +368,131 @@ export default class Table extends Component {
|
|
|
147
368
|
}
|
|
148
369
|
}
|
|
149
370
|
window.customElements.define('k-table', Table);
|
|
371
|
+
|
|
372
|
+
class PageSelect extends Component {
|
|
373
|
+
constructor(table) {
|
|
374
|
+
super();
|
|
375
|
+
this.table = table;
|
|
376
|
+
this.classList.add('mxq');
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
connectedCallback() {
|
|
380
|
+
this.render();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async render() {
|
|
384
|
+
if(await super.render()){
|
|
385
|
+
this.updateOptions();
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
return false;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
updateOptions() {
|
|
392
|
+
const $select = this.shadowRoot.getElementById('pageSelect');
|
|
393
|
+
$select.innerHTML = '';
|
|
394
|
+
for (let i = 1; i <= this.table.getTotalPages(); i++) {
|
|
395
|
+
const $option = document.createElement('option');
|
|
396
|
+
$option.value = i;
|
|
397
|
+
$option.textContent = i;
|
|
398
|
+
$select.appendChild($option);
|
|
399
|
+
}
|
|
400
|
+
$select.value = this.table.getCurrentPage();
|
|
401
|
+
const $totalPages = this.shadowRoot.getElementById('totalPages');
|
|
402
|
+
$totalPages.textContent = this.table.getTotalPages();
|
|
403
|
+
|
|
404
|
+
onEvent($select, 'change', () => {
|
|
405
|
+
this.table.setPage(parseInt($select.value));
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
onEvent(this.table, 'pageSizeChange', () => {
|
|
409
|
+
this.updateOptions();
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
onEvent(this.table, 'pageChange', () => {
|
|
413
|
+
$select.value = this.table.getCurrentPage();
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
get shadowTemplate() {
|
|
418
|
+
return /*html*/`
|
|
419
|
+
<select id="pageSelect" class="mxq"></select>
|
|
420
|
+
<label> out of <span id="totalPages"></span></label>
|
|
421
|
+
`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
get shadowStyles() {
|
|
425
|
+
return /*css*/`
|
|
426
|
+
:host {
|
|
427
|
+
display: inline-flex;
|
|
428
|
+
width: max-content;
|
|
429
|
+
align-items: baseline;
|
|
430
|
+
}
|
|
431
|
+
#pageSelect, label {
|
|
432
|
+
display: inline;
|
|
433
|
+
}
|
|
434
|
+
label {
|
|
435
|
+
white-space: nowrap;
|
|
436
|
+
}
|
|
437
|
+
`;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
window.customElements.define('k-table-page-select', PageSelect);
|
|
441
|
+
|
|
442
|
+
class PageSize extends Component {
|
|
443
|
+
constructor(table) {
|
|
444
|
+
super();
|
|
445
|
+
this.table = table;
|
|
446
|
+
this.classList.add('mxq');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
connectedCallback() {
|
|
450
|
+
this.render();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async render() {
|
|
454
|
+
if(await super.render()){
|
|
455
|
+
const $select = this.shadowRoot.getElementById('pageSizeSelect');
|
|
456
|
+
const options = this.table.pageSizeOptions;
|
|
457
|
+
options.forEach(size => {
|
|
458
|
+
const $option = document.createElement('option');
|
|
459
|
+
$option.value = size;
|
|
460
|
+
$option.textContent = size;
|
|
461
|
+
$select.appendChild($option);
|
|
462
|
+
});
|
|
463
|
+
$select.value = this.table.pageSize;
|
|
464
|
+
|
|
465
|
+
onEvent($select, 'change', () => {
|
|
466
|
+
this.table.setPageSize(parseInt($select.value));
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
onEvent(this.table, 'pageSizeChange', () => {
|
|
470
|
+
$select.value = this.table.pageSize;
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
get shadowTemplate() {
|
|
479
|
+
return /*html*/`
|
|
480
|
+
<label for="pageSizeSelect">Page Size: </label>
|
|
481
|
+
<select id="pageSizeSelect" class="mxq"></select>
|
|
482
|
+
`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
get shadowStyles() {
|
|
486
|
+
return /*css*/`
|
|
487
|
+
:host {
|
|
488
|
+
display: inline-flex;
|
|
489
|
+
width: max-content;
|
|
490
|
+
align-items: baseline;
|
|
491
|
+
}
|
|
492
|
+
label {
|
|
493
|
+
white-space: nowrap;
|
|
494
|
+
}
|
|
495
|
+
`;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
window.customElements.define('k-table-page-size', PageSize);
|
package/README.md
DELETED