@uwdata/mosaic-inputs 0.1.0 → 0.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uwdata/mosaic-inputs",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Mosaic input components.",
5
5
  "keywords": [
6
6
  "inputs",
@@ -25,9 +25,9 @@
25
25
  "prepublishOnly": "npm run test && npm run lint && npm run build"
26
26
  },
27
27
  "dependencies": {
28
- "@uwdata/mosaic-core": "^0.1.0",
29
- "@uwdata/mosaic-sql": "^0.1.0",
28
+ "@uwdata/mosaic-core": "^0.3.0",
29
+ "@uwdata/mosaic-sql": "^0.3.0",
30
30
  "isoformat": "^0.2.1"
31
31
  },
32
- "gitHead": "a7967c35349bdf7f00abb113ce1dd9abb233cd62"
32
+ "gitHead": "a8dd23fed4c7a24c0a2ee5261d1aabe4239ce574"
33
33
  }
package/src/Menu.js CHANGED
@@ -1,12 +1,16 @@
1
1
  import { MosaicClient, isParam, isSelection } from '@uwdata/mosaic-core';
2
2
  import { Query, eq, literal } from '@uwdata/mosaic-sql';
3
+ import { input } from './input.js';
3
4
 
4
5
  const isObject = v => {
5
6
  return v && typeof v === 'object' && !Array.isArray(v);
6
7
  };
7
8
 
9
+ export const menu = options => input(Menu, options);
10
+
8
11
  export class Menu extends MosaicClient {
9
12
  constructor({
13
+ element,
10
14
  filterBy,
11
15
  from,
12
16
  column,
@@ -22,7 +26,7 @@ export class Menu extends MosaicClient {
22
26
  this.selection = as;
23
27
  this.format = format;
24
28
 
25
- this.element = document.createElement('div');
29
+ this.element = element ?? document.createElement('div');
26
30
  this.element.setAttribute('class', 'input');
27
31
  this.element.value = this;
28
32
 
@@ -36,13 +40,12 @@ export class Menu extends MosaicClient {
36
40
  this.update();
37
41
  }
38
42
  value = value ?? this.selection?.value ?? this.data?.[0]?.value;
39
- this.select.value = value;
40
43
  if (this.selection?.value === undefined) this.publish(value);
41
44
  this.element.appendChild(this.select);
42
45
 
43
46
  if (this.selection) {
44
47
  this.select.addEventListener('input', () => {
45
- this.publish(this.selectedValue() || null);
48
+ this.publish(this.selectedValue() ?? null);
46
49
  });
47
50
  if (!isSelection(this.selection)) {
48
51
  this.selection.addEventListener('value', value => {
@@ -59,11 +62,19 @@ export class Menu extends MosaicClient {
59
62
  const index = this.select.selectedIndex;
60
63
  return this.data[index].value;
61
64
  } else {
62
- // TODO make more robust
63
- this.select.value = String(value);
65
+ const index = this.data?.findIndex(opt => opt.value === value);
66
+ if (index >= 0) {
67
+ this.select.selectedIndex = index;
68
+ } else {
69
+ this.select.value = String(value);
70
+ }
64
71
  }
65
72
  }
66
73
 
74
+ reset() {
75
+ this.select.selectedIndex = this.from ? 0 : -1;
76
+ }
77
+
67
78
  publish(value) {
68
79
  const { selection, column } = this;
69
80
  if (isSelection(selection)) {
@@ -104,7 +115,7 @@ export class Menu extends MosaicClient {
104
115
  this.select.appendChild(opt);
105
116
  }
106
117
  if (this.selection) {
107
- this.select.value = this.selection?.value || '';
118
+ this.selectedValue(this.selection?.value ?? '');
108
119
  }
109
120
  return this;
110
121
  }
package/src/Search.js CHANGED
@@ -2,17 +2,21 @@ import { MosaicClient, isParam, isSelection } from '@uwdata/mosaic-core';
2
2
  import {
3
3
  Query, regexp_matches, contains, prefix, suffix, literal
4
4
  } from '@uwdata/mosaic-sql';
5
+ import { input } from './input.js';
5
6
 
6
7
  const FUNCTIONS = { contains, prefix, suffix, regexp: regexp_matches };
7
8
  let _id = 0;
8
9
 
10
+ export const search = options => input(Search, options);
11
+
9
12
  export class Search extends MosaicClient {
10
13
  constructor({
14
+ element,
11
15
  filterBy,
12
16
  from,
13
17
  column,
14
18
  label,
15
- type,
19
+ type = 'contains',
16
20
  as
17
21
  } = {}) {
18
22
  super(filterBy);
@@ -22,7 +26,7 @@ export class Search extends MosaicClient {
22
26
  this.column = column;
23
27
  this.selection = as;
24
28
 
25
- this.element = document.createElement('div');
29
+ this.element = element ?? document.createElement('div');
26
30
  this.element.setAttribute('class', 'input');
27
31
  this.element.value = this;
28
32
 
@@ -53,6 +57,10 @@ export class Search extends MosaicClient {
53
57
  }
54
58
  }
55
59
 
60
+ reset() {
61
+ this.searchbox.value = '';
62
+ }
63
+
56
64
  publish(value) {
57
65
  const { selection, column, type } = this;
58
66
  if (isSelection(selection)) {
package/src/Slider.js CHANGED
@@ -1,10 +1,14 @@
1
1
  import { MosaicClient, isParam, isSelection } from '@uwdata/mosaic-core';
2
2
  import { Query, eq, literal, max, min } from '@uwdata/mosaic-sql';
3
+ import { input } from './input.js';
3
4
 
4
5
  let _id = 0;
5
6
 
7
+ export const slider = options => input(Slider, options);
8
+
6
9
  export class Slider extends MosaicClient {
7
10
  constructor({
11
+ element,
8
12
  filterBy,
9
13
  as,
10
14
  min,
@@ -13,7 +17,8 @@ export class Slider extends MosaicClient {
13
17
  from,
14
18
  column,
15
19
  label = column,
16
- value = as?.value
20
+ value = as?.value,
21
+ width
17
22
  } = {}) {
18
23
  super(filterBy);
19
24
  this.id = 'slider_' + (++_id);
@@ -24,7 +29,7 @@ export class Slider extends MosaicClient {
24
29
  this.max = max;
25
30
  this.step = step;
26
31
 
27
- this.element = document.createElement('div');
32
+ this.element = element || document.createElement('div');
28
33
  this.element.setAttribute('class', 'input');
29
34
  this.element.value = this;
30
35
 
@@ -38,6 +43,7 @@ export class Slider extends MosaicClient {
38
43
  this.slider = document.createElement('input');
39
44
  this.slider.setAttribute('id', this.id);
40
45
  this.slider.setAttribute('type', 'range');
46
+ if (width != null) this.slider.style.width = `${+width}px`;
41
47
  if (min != null) this.slider.setAttribute('min', min);
42
48
  if (max != null) this.slider.setAttribute('max', max);
43
49
  if (step != null) this.slider.setAttribute('step', step);
package/src/Table.js CHANGED
@@ -1,24 +1,33 @@
1
- import { MosaicClient } from '@uwdata/mosaic-core';
1
+ import { MosaicClient, coordinator } from '@uwdata/mosaic-core';
2
2
  import { Query, column, desc } from '@uwdata/mosaic-sql';
3
3
  import { formatDate, formatLocaleAuto, formatLocaleNumber } from './util/format.js';
4
+ import { input } from './input.js';
4
5
 
5
6
  let _id = -1;
6
7
 
8
+ export const table = options => input(Table, options);
9
+
7
10
  export class Table extends MosaicClient {
8
11
  constructor({
12
+ element,
9
13
  filterBy,
10
14
  from,
11
15
  columns = ['*'],
16
+ align = {},
12
17
  format,
13
- rowBatch = 100,
14
18
  width,
15
- height = 500
19
+ maxWidth,
20
+ height = 500,
21
+ rowBatch = 100,
16
22
  } = {}) {
17
23
  super(filterBy);
18
24
  this.id = `table-${++_id}`;
19
25
  this.from = from;
20
26
  this.columns = columns;
21
27
  this.format = format;
28
+ this.align = align;
29
+ this.widths = typeof width === 'object' ? width : {};
30
+
22
31
  this.offset = 0;
23
32
  this.limit = +rowBatch;
24
33
  this.pending = false;
@@ -27,12 +36,11 @@ export class Table extends MosaicClient {
27
36
  this.sortColumn = null;
28
37
  this.sortDesc = false;
29
38
 
30
- this.element = document.createElement('div');
39
+ this.element = element || document.createElement('div');
31
40
  this.element.setAttribute('id', this.id);
32
41
  this.element.value = this;
33
- if (width) {
34
- this.element.style.maxWidth = `${width}px`;
35
- }
42
+ if (typeof width === 'number') this.element.style.width = `${width}px`;
43
+ if (maxWidth) this.element.style.maxWidth = `${maxWidth}px`;
36
44
  this.element.style.maxHeight = `${height}px`;
37
45
  this.element.style.overflow = 'auto';
38
46
 
@@ -47,9 +55,7 @@ export class Table extends MosaicClient {
47
55
 
48
56
  if (scrollHeight - scrollTop < 2 * clientHeight) {
49
57
  this.pending = true;
50
- this.offset += this.limit;
51
- const query = this.queryInternal(this.filterBy?.predicate(this));
52
- this.requestQuery(query);
58
+ this.requestData(this.offset + this.limit);
53
59
  }
54
60
  });
55
61
 
@@ -66,17 +72,28 @@ export class Table extends MosaicClient {
66
72
  this.element.appendChild(this.style);
67
73
  }
68
74
 
75
+ requestData(offset = 0) {
76
+ this.offset = offset;
77
+
78
+ // request next data batch
79
+ const query = this.query(this.filterBy?.predicate(this));
80
+ this.requestQuery(query);
81
+
82
+ // prefetch subsequent data batch
83
+ coordinator().prefetch(query.clone().offset(offset + this.limit));
84
+ }
85
+
69
86
  fields() {
70
87
  return this.columns.map(name => column(this.from, name));
71
88
  }
72
89
 
73
- fieldStats(stats) {
74
- this.stats = stats;
90
+ fieldInfo(info) {
91
+ this.schema = info;
75
92
 
76
93
  const thead = this.head;
77
94
  thead.innerHTML = '';
78
95
  const tr = document.createElement('tr');
79
- for (const { column } of stats) {
96
+ for (const { column } of info) {
80
97
  const th = document.createElement('th');
81
98
  th.addEventListener('click', evt => this.sort(evt, column));
82
99
  th.appendChild(document.createElement('span'));
@@ -86,23 +103,22 @@ export class Table extends MosaicClient {
86
103
  thead.appendChild(tr);
87
104
 
88
105
  // get column formatters
89
- this.formats = formatof(this.format, stats);
106
+ this.formats = formatof(this.format, info);
90
107
 
91
108
  // get column alignment style
92
- this.style.innerText = tableCSS(this.id, alignof({}, stats));
109
+ this.style.innerText = tableCSS(
110
+ this.id,
111
+ alignof(this.align, info),
112
+ widthof(this.widths, info)
113
+ );
93
114
 
94
115
  return this;
95
116
  }
96
117
 
97
- query(filter) {
98
- this.offset = 0;
99
- return this.queryInternal(filter);
100
- }
101
-
102
- queryInternal(filter = []) {
103
- const { from, limit, offset, stats, sortColumn, sortDesc } = this;
118
+ query(filter = []) {
119
+ const { from, limit, offset, schema, sortColumn, sortDesc } = this;
104
120
  return Query.from(from)
105
- .select(stats.map(s => s.column))
121
+ .select(schema.map(s => s.column))
106
122
  .where(filter)
107
123
  .orderby(sortColumn ? (sortDesc ? desc(sortColumn) : sortColumn) : [])
108
124
  .limit(limit)
@@ -120,15 +136,15 @@ export class Table extends MosaicClient {
120
136
  }
121
137
 
122
138
  update() {
123
- const { body, formats, data, stats, limit } = this;
124
- const nf = stats.length;
139
+ const { body, formats, data, schema, limit } = this;
140
+ const nf = schema.length;
125
141
 
126
142
  let count = 0;
127
143
  for (const row of data) {
128
144
  ++count;
129
145
  const tr = document.createElement('tr');
130
146
  for (let i = 0; i < nf; ++i) {
131
- const value = row[stats[i].column];
147
+ const value = row[schema[i].column];
132
148
  const td = document.createElement('td');
133
149
  td.innerText = value == null ? '' : formats[i](value);
134
150
  tr.appendChild(td);
@@ -166,13 +182,12 @@ export class Table extends MosaicClient {
166
182
  }
167
183
 
168
184
  // issue query for sorted data
169
- const query = this.query(this.filterBy?.predicate(this));
170
- this.requestQuery(query);
185
+ this.requestData();
171
186
  }
172
187
  }
173
188
 
174
- function formatof(base = {}, stats, locale) {
175
- return stats.map(({ column, type }) => {
189
+ function formatof(base = {}, schema, locale) {
190
+ return schema.map(({ column, type }) => {
176
191
  if (column in base) {
177
192
  return base[column];
178
193
  } else {
@@ -185,8 +200,8 @@ function formatof(base = {}, stats, locale) {
185
200
  });
186
201
  }
187
202
 
188
- function alignof(base = {}, stats) {
189
- return stats.map(({ column, type }) => {
203
+ function alignof(base = {}, schema) {
204
+ return schema.map(({ column, type }) => {
190
205
  if (column in base) {
191
206
  return base[column];
192
207
  } else if (type === 'number') {
@@ -197,11 +212,18 @@ function alignof(base = {}, stats) {
197
212
  });
198
213
  }
199
214
 
200
- function tableCSS(id, align) {
215
+ function widthof(base = {}, schema) {
216
+ return schema.map(({ column }) => base[column]);
217
+ }
218
+
219
+ function tableCSS(id, aligns, widths) {
201
220
  const styles = [];
202
- align.forEach((a, i) => {
203
- if (a !== 'left') {
204
- styles.push(`#${id} tr>:nth-child(${i+1}) {text-align:${a}}`);
221
+ aligns.forEach((a, i) => {
222
+ const w = +widths[i];
223
+ if (a !== 'left' || w) {
224
+ const align = a !== 'left' ? `text-align:${a};` : '';
225
+ const width = w ? `width:${w}px;max-width:${w}px;` : '';
226
+ styles.push(`#${id} tr>:nth-child(${i+1}) {${align}${width}}`);
205
227
  }
206
228
  });
207
229
  return styles.join(' ');
package/src/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { Menu } from './Menu.js';
2
- export { Search } from './Search.js';
3
- export { Slider } from './Slider.js';
4
- export { Table } from './Table.js';
1
+ export { Menu, menu } from './Menu.js';
2
+ export { Search, search } from './Search.js';
3
+ export { Slider, slider } from './Slider.js';
4
+ export { Table, table } from './Table.js';
package/src/input.js ADDED
@@ -0,0 +1,7 @@
1
+ import { coordinator } from '@uwdata/mosaic-core';
2
+
3
+ export function input(InputClass, options) {
4
+ const input = new InputClass(options);
5
+ coordinator().connect(input);
6
+ return input.element;
7
+ }