@teipublisher/pb-components 1.43.5 → 1.44.1

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.
@@ -1,189 +1,227 @@
1
- import { LitElement, html, css } from 'lit-element';
2
- import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
3
- import { pbMixin } from './pb-mixin.js';
4
- import { themableMixin } from "./theming.js";
5
-
6
- /**
7
- * Implements a list which is split into different categories
8
- * (e.g. letters of the alphabet, countries ...).
9
- * Only one category is shown at a time unless the server reports
10
- * no categories (e.g. if the number of items to display goes below
11
- * a defined threshold).
12
- *
13
- * The server-side API endpoint should return a JSON object with two
14
- * properties:
15
- *
16
- * + `categories`: an array of category descriptions: each item should
17
- * be an object with two properties: `category` - containing the name of the category
18
- * and `count` - containing a count of items available under this category.
19
- * + `items`: an array with the items to be shown for the currently selected
20
- * category. Those may contain HTML markup.
21
- *
22
- * @cssprop --pb-categorized-list-columns - the number of columns to display (default: 2)
23
- * @fires pb-submit - when received, submit a request to the server and refresh
24
- * @fires pb-start-update - sent before the element sends the request to the server
25
- * @fires pb-end-update - sent after new content has been received
26
- */
27
- export class PbSplitList extends themableMixin(pbMixin(LitElement)) {
28
- static get properties() {
29
- return {
30
- /**
31
- * Server-side API endpoint to retrieve items from
32
- */
33
- url: {
34
- type: String
35
- },
36
- /**
37
- * The initially selected category
38
- */
39
- selected: {
40
- type: String
41
- },
42
- /**
43
- * A CSS selector pointing to one or more `pb-custom-form`
44
- * instances. The element will collect additional parameters
45
- * from those forms and includes them in the request to the server
46
- */
47
- subforms: {
48
- type: String
49
- },
50
- _categories: {
51
- type: Array
52
- },
53
- ...super.properties
54
- };
55
- }
56
-
57
- constructor() {
58
- super();
59
- this._categories = [];
60
- this._params = {};
61
- this.selected = null;
62
- this.subforms = null;
63
- }
64
-
65
- connectedCallback() {
66
- super.connectedCallback();
67
-
68
- this.selected = this.getParameter('category', this.selected);
69
-
70
- window.addEventListener('popstate', (ev) => {
71
- console.log('<pb-split-list> popstate: %o', ev);
72
- this.selected = ev.state.category;
73
- this.submit();
74
- });
75
-
76
- this.subscribeTo('pb-submit', this.load.bind(this));
77
- }
78
-
79
- firstUpdated() {
80
- super.firstUpdated();
81
-
82
- PbSplitList.waitOnce('pb-page-ready', () => {
83
- this.load();
84
- });
85
- }
86
-
87
- submit() {
88
- this.load();
89
- }
90
-
91
- load() {
92
- const formParams = this._paramsFromSubforms({ category: this.selected });
93
- this.setParameters(formParams);
94
- this.pushHistory('pb-split-list', formParams);
95
-
96
- const params = new URLSearchParams(formParams);
97
-
98
- const url = `${this.toAbsoluteURL(this.url)}?${params.toString()}`;
99
- console.log(`<pb-split-list> Fetching from URL: ${url}`);
100
-
101
- this.emitTo('pb-start-update');
102
-
103
- fetch(url)
104
- .then((response) => {
105
- if (response.ok) {
106
- return response.json();
107
- }
108
- return Promise.reject(response.status);
109
- })
110
- .then((json) => {
111
- this._categories = json.categories;
112
- this.innerHTML = json.items.join('');
113
- this.emitTo('pb-end-update');
114
- })
115
- .catch((error) => {
116
- console.error(`<pb-split-list> Error caught: ${error}`);
117
- this.emitTo('pb-end-update');
118
- });
119
- }
120
-
121
- _selectCategory(ev, category) {
122
- ev.preventDefault();
123
- this.selected = category;
124
- this.load();
125
- }
126
-
127
- _paramsFromSubforms(params) {
128
- if (this.subforms) {
129
- document.querySelectorAll(this.subforms).forEach((form) => {
130
- if (form.serializeForm) {
131
- Object.assign(params, form.serializeForm());
132
- }
133
- });
134
- }
135
- return params;
136
- }
137
-
138
- render() {
139
- return html`
140
- <header>
141
- ${
142
- this._categories.map((cat) =>
143
- html`
144
- <a part="${this.selected === cat.category ? 'active-category' : 'category'}" href="#${cat.category}" title="${cat.count}" class="${this.selected === cat.category ? 'active' : ''}"
145
- @click="${(ev) => this._selectCategory(ev, cat.category)}">
146
- ${cat.label ? unsafeHTML(cat.label) : cat.category}
147
- </a>
148
- `
149
- )
150
- }
151
- </header>
152
- <div id="items" part="items"><slot></slot></div>
153
- `;
154
- }
155
-
156
- static get styles() {
157
- return css`
158
- :host {
159
- display: block;
160
- }
161
-
162
- header {
163
- display: flex;
164
- flex-wrap: wrap;
165
- column-gap: 10px;
166
- width: 100%;
167
- }
168
-
169
- #items {
170
- display: grid;
171
- grid-template-columns: repeat(var(--pb-categorized-list-columns, 2), auto);
172
- grid-auto-rows: 1fr;
173
- column-gap: 10px;
174
- width: 100%;
175
- }
176
-
177
- [part=category], #items a {
178
- text-decoration: none;
179
- color: var(--pb-link-color);
180
- }
181
-
182
- [part=active-category] {
183
- text-decoration: none;
184
- color: var(--pb-highlight-color);
185
- }
186
- `;
187
- }
188
- }
1
+ import { LitElement, html, css } from 'lit-element';
2
+ import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
3
+ import { pbMixin } from './pb-mixin.js';
4
+ import { themableMixin } from "./theming.js";
5
+
6
+ /**
7
+ * Implements a list which is split into different categories
8
+ * (e.g. letters of the alphabet, countries ...).
9
+ * Only one category is shown at a time unless the server reports
10
+ * no categories (e.g. if the number of items to display goes below
11
+ * a defined threshold).
12
+ *
13
+ * The server-side API endpoint should return a JSON object with two
14
+ * properties:
15
+ *
16
+ * + `categories`: an array of category descriptions: each item should
17
+ * be an object with two properties: `category` - containing the name of the category
18
+ * and `count` - containing a count of items available under this category.
19
+ * + `items`: an array with the items to be shown for the currently selected
20
+ * category. Those may contain HTML markup.
21
+ *
22
+ * Sample JSON object for pb-split-list
23
+ * ```javascript
24
+ * {
25
+ * "items": [
26
+ * "<span><a href='Abegg-Arter Carl?category=A&amp;view=correspondents&amp;search='>Abegg-Arter, Carl</a><span class='dates'> (1836–1912)</span></span>",
27
+ * "<span><a href='Abegg Hans Heinrich?category=A&amp;view=correspondents&amp;search='>Abegg, Hans Heinrich</a><span class='dates'> (1805–1874)</span></span>",
28
+ * "<span><a href='Abegg Jakob?category=A&amp;view=correspondents&amp;search='>Abegg, Jakob</a><span class='dates'> (1801–1871)</span></span>",
29
+ * "<span><a href='Abys Raget?category=A&amp;view=correspondents&amp;search='>Abys, Raget</a><span class='dates'> (1790–1861)</span></span>",
30
+ * "<span><a href='Aebli Johann Peter?category=A&amp;view=correspondents&amp;search='>Aebli, Johann Peter</a><span class='dates'> (1804–1879)</span></span>",
31
+ * "<span><a href='Aepli Arnold Otto?category=A&amp;view=correspondents&amp;search='>Aepli, Arnold Otto</a><span class='dates'> (1816–1897)</span></span>",
32
+ * ...
33
+ * ],
34
+ * "categories": [
35
+ * {
36
+ * "category": "A",
37
+ * "count": 22
38
+ * },
39
+ * {
40
+ * "category": "B",
41
+ * "count": 77
42
+ * },
43
+ * {
44
+ * "category": "C",
45
+ * "count": 19
46
+ * },
47
+ * ...
48
+ * ]
49
+ * }
50
+ * ```
51
+ *
52
+ * Sample Usage
53
+ * ```xml
54
+ * <pb-split-list url="api/people" subforms="#options" selected="A" emit="transcription" subscribe="transcription"></pb-split-list>
55
+ * ```
56
+ * See https://www.briefedition.alfred-escher.ch/kontexte/personen/?category=A&search=&view=correspondents for a running sample. The source code of the webpage is here: https://github.com/stazh/briefedition-escher. Relevant files are:
57
+ * - [templates/index.html](https://github.com/stazh/briefedition-escher/blob/master/templates/index.html#L223) - usage of pb-timeline
58
+ * - [modules/custom-api.json](https://github.com/stazh/briefedition-escher/blob/master/modules/custom-api.json#L1098) - `/api/people` endpoint delivering required JSON object
59
+ *
60
+ * @cssprop --pb-categorized-list-columns - the number of columns to display (default: 2)
61
+ * @fires pb-submit - when received, submit a request to the server and refresh
62
+ * @fires pb-start-update - sent before the element sends the request to the server
63
+ * @fires pb-end-update - sent after new content has been received
64
+ */
65
+ export class PbSplitList extends themableMixin(pbMixin(LitElement)) {
66
+ static get properties() {
67
+ return {
68
+ /**
69
+ * Server-side API endpoint to retrieve items from
70
+ */
71
+ url: {
72
+ type: String
73
+ },
74
+ /**
75
+ * The initially selected category
76
+ */
77
+ selected: {
78
+ type: String
79
+ },
80
+ /**
81
+ * A CSS selector pointing to one or more `pb-custom-form`
82
+ * instances. The element will collect additional parameters
83
+ * from those forms and includes them in the request to the server
84
+ */
85
+ subforms: {
86
+ type: String
87
+ },
88
+ _categories: {
89
+ type: Array
90
+ },
91
+ ...super.properties
92
+ };
93
+ }
94
+
95
+ constructor() {
96
+ super();
97
+ this._categories = [];
98
+ this._params = {};
99
+ this.selected = null;
100
+ this.subforms = null;
101
+ }
102
+
103
+ connectedCallback() {
104
+ super.connectedCallback();
105
+
106
+ this.selected = this.getParameter('category', this.selected);
107
+
108
+ window.addEventListener('popstate', (ev) => {
109
+ console.log('<pb-split-list> popstate: %o', ev);
110
+ this.selected = ev.state.category;
111
+ this.submit();
112
+ });
113
+
114
+ this.subscribeTo('pb-submit', this.load.bind(this));
115
+ }
116
+
117
+ firstUpdated() {
118
+ super.firstUpdated();
119
+
120
+ PbSplitList.waitOnce('pb-page-ready', () => {
121
+ this.load();
122
+ });
123
+ }
124
+
125
+ submit() {
126
+ this.load();
127
+ }
128
+
129
+ load() {
130
+ const formParams = this._paramsFromSubforms({ category: this.selected });
131
+ this.setParameters(formParams);
132
+ this.pushHistory('pb-split-list', formParams);
133
+
134
+ const params = new URLSearchParams(formParams);
135
+
136
+ const url = `${this.toAbsoluteURL(this.url)}?${params.toString()}`;
137
+ console.log(`<pb-split-list> Fetching from URL: ${url}`);
138
+
139
+ this.emitTo('pb-start-update');
140
+
141
+ fetch(url)
142
+ .then((response) => {
143
+ if (response.ok) {
144
+ return response.json();
145
+ }
146
+ return Promise.reject(response.status);
147
+ })
148
+ .then((json) => {
149
+ this._categories = json.categories;
150
+ this.innerHTML = json.items.join('');
151
+ this.emitTo('pb-end-update');
152
+ })
153
+ .catch((error) => {
154
+ console.error(`<pb-split-list> Error caught: ${error}`);
155
+ this.emitTo('pb-end-update');
156
+ });
157
+ }
158
+
159
+ _selectCategory(ev, category) {
160
+ ev.preventDefault();
161
+ this.selected = category;
162
+ this.load();
163
+ }
164
+
165
+ _paramsFromSubforms(params) {
166
+ if (this.subforms) {
167
+ document.querySelectorAll(this.subforms).forEach((form) => {
168
+ if (form.serializeForm) {
169
+ Object.assign(params, form.serializeForm());
170
+ }
171
+ });
172
+ }
173
+ return params;
174
+ }
175
+
176
+ render() {
177
+ return html`
178
+ <header>
179
+ ${
180
+ this._categories.map((cat) =>
181
+ html`
182
+ <a part="${this.selected === cat.category ? 'active-category' : 'category'}" href="#${cat.category}" title="${cat.count}" class="${this.selected === cat.category ? 'active' : ''}"
183
+ @click="${(ev) => this._selectCategory(ev, cat.category)}">
184
+ ${cat.label ? unsafeHTML(cat.label) : cat.category}
185
+ </a>
186
+ `
187
+ )
188
+ }
189
+ </header>
190
+ <div id="items" part="items"><slot></slot></div>
191
+ `;
192
+ }
193
+
194
+ static get styles() {
195
+ return css`
196
+ :host {
197
+ display: block;
198
+ }
199
+
200
+ header {
201
+ display: flex;
202
+ flex-wrap: wrap;
203
+ column-gap: 10px;
204
+ width: 100%;
205
+ }
206
+
207
+ #items {
208
+ display: grid;
209
+ grid-template-columns: repeat(var(--pb-categorized-list-columns, 2), auto);
210
+ grid-auto-rows: 1fr;
211
+ column-gap: 10px;
212
+ width: 100%;
213
+ }
214
+
215
+ [part=category], #items a {
216
+ text-decoration: none;
217
+ color: var(--pb-link-color);
218
+ }
219
+
220
+ [part=active-category] {
221
+ text-decoration: none;
222
+ color: var(--pb-highlight-color);
223
+ }
224
+ `;
225
+ }
226
+ }
189
227
  customElements.define('pb-split-list', PbSplitList);