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