x4js 2.3.17 → 2.3.19
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/aicli.md +3 -3
- package/aicontext.md +211 -47
- package/cli/config.mjs +14 -7
- package/cli/copy-plugin.mjs +8 -0
- package/cli/create.mjs +8 -2
- package/cli/dev.mjs +4 -1
- package/cli/less-plugin.mjs +5 -1
- package/cli/main.mjs +15 -5
- package/cli/rawfile-plugin.mjs +23 -3
- package/cli/templates.mjs +5 -1
- package/package.json +1 -1
- package/src/components/calendar/calendar.module.scss +16 -1
- package/src/components/calendar/calendar.ts +52 -9
- package/src/components/checkbox/checkbox.ts +1 -0
- package/src/components/combobox/combobox.ts +51 -23
- package/src/components/dialog/dialog.ts +4 -1
- package/src/components/form/form.ts +9 -5
- package/src/components/gridview/gridview.ts +352 -176
- package/src/components/input/input.ts +63 -38
- package/src/components/keyboard/keyboard.module.scss +35 -4
- package/src/components/keyboard/keyboard.ts +295 -80
- package/src/components/listbox/listbox.ts +6 -1
- package/src/components/messages/messages.ts +12 -3
- package/src/components/popup/popup.ts +19 -20
- package/src/components/radio/radio.ts +8 -4
- package/src/components/rating/rating.ts +38 -48
- package/src/components/slider/slider.ts +38 -8
- package/src/components/spreadsheet/spreadsheet.ts +167 -132
- package/src/components/switch/switch.ts +2 -1
- package/src/components/tag/tag.ts +1 -2
- package/src/components/textarea/textarea.module.scss +6 -2
- package/src/components/textarea/textarea.ts +50 -11
- package/src/components/textedit/textedit.ts +12 -2
- package/src/core/component.ts +16 -9
- package/src/core/core_application.ts +8 -4
- package/src/core/core_clipboard.ts +239 -0
- package/src/core/core_data.ts +118 -49
- package/src/core/core_dom.ts +36 -6
- package/src/core/core_dragdrop.ts +11 -5
- package/src/core/core_element.ts +1 -1
- package/src/core/core_pdf.ts +696 -46
- package/src/core/core_router.ts +42 -44
- package/src/core/core_shortcuts.ts +1 -1
- package/src/core/core_state.ts +22 -5
- package/src/x4.d.ts +1 -0
- package/src/x4.ts +1 -0
|
@@ -80,6 +80,12 @@ export interface GridviewProps extends ComponentProps {
|
|
|
80
80
|
columns: GridColumn[];
|
|
81
81
|
emptyMsg?: string;
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* several rows can be selected: Ctrl (Cmd on a Mac) + click adds a row or
|
|
85
|
+
* removes it, Shift + click selects up to the row, Ctrl + A selects all
|
|
86
|
+
*/
|
|
87
|
+
multisel?: boolean;
|
|
88
|
+
|
|
83
89
|
click?: EventCallback<EvClick>;
|
|
84
90
|
dblClick?: EventCallback<EvDblClick>;
|
|
85
91
|
contextMenu?: EventCallback<EvContextMenu>;
|
|
@@ -155,7 +161,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
155
161
|
private _start: number;
|
|
156
162
|
private _end: number;
|
|
157
163
|
|
|
158
|
-
private _selection: Set<
|
|
164
|
+
private _selection: Set<any>; // the ids of the selected records: a row changes place when the view is sorted
|
|
159
165
|
|
|
160
166
|
|
|
161
167
|
// TODO: that
|
|
@@ -195,11 +201,26 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
195
201
|
this.setAttribute("tabindex", 0);
|
|
196
202
|
|
|
197
203
|
this.addDOMEvent("created", () => {
|
|
204
|
+
// back in the document: the view follows its store again (see "removed")
|
|
205
|
+
if (this._dataview) {
|
|
206
|
+
this._dataview.open( );
|
|
207
|
+
}
|
|
208
|
+
|
|
198
209
|
this._init();
|
|
199
210
|
this._dirty = 1;
|
|
200
211
|
this.lock(false);
|
|
201
212
|
});
|
|
202
213
|
|
|
214
|
+
// out of the document: its store must not keep the grid alive, nor redraw it
|
|
215
|
+
this.addDOMEvent("removed", () => {
|
|
216
|
+
if (this._dataview) {
|
|
217
|
+
this._dataview.close( );
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// as before its first insertion: nothing is drawn out of the document
|
|
221
|
+
this.lock( true );
|
|
222
|
+
});
|
|
223
|
+
|
|
203
224
|
this.addDOMEvent("resized", () => {
|
|
204
225
|
this._updateFlexs( );
|
|
205
226
|
this._computeFullSize( );
|
|
@@ -210,6 +231,200 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
210
231
|
this._on_key( e );
|
|
211
232
|
})
|
|
212
233
|
|
|
234
|
+
// the handlers of the grid itself are put once, here. _init builds what is
|
|
235
|
+
// inside the grid, each time it comes in the document
|
|
236
|
+
|
|
237
|
+
// WHEEL
|
|
238
|
+
this.addDOMEvent("wheel", (ev: WheelEvent) => {
|
|
239
|
+
// many rows: one pixel of the scroll bar is one row, wherever the pointer is
|
|
240
|
+
if (ev.deltaY && this._dataview && this._dataview.getCount() >= SCROLL_LIMIT) {
|
|
241
|
+
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -1 : 1);
|
|
242
|
+
ev.stopPropagation();
|
|
243
|
+
ev.preventDefault();
|
|
244
|
+
}
|
|
245
|
+
else if (this._has_fixed && ev.deltaY) {
|
|
246
|
+
// wheel on fixed part
|
|
247
|
+
// fixed part do not have scrollbar, so we need to handle it by hand
|
|
248
|
+
let t = ev.target as Node;
|
|
249
|
+
while (t != this.dom) {
|
|
250
|
+
if (t == this._vheader.dom) {
|
|
251
|
+
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -this._row_height : this._row_height);
|
|
252
|
+
ev.stopPropagation();
|
|
253
|
+
ev.preventDefault();
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
t = t.parentNode;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
const targetRow = ( e: MouseEvent ) => {
|
|
263
|
+
let el = Component.parentElement(e.target as HTMLElement, Component);
|
|
264
|
+
while (el && !el.hasClass("row")) {
|
|
265
|
+
el = el.parentElement();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (el) {
|
|
269
|
+
return el.getIntData("row");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return undefined;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const inColHeader = ( e: MouseEvent ) => {
|
|
276
|
+
let el = Component.parentElement(e.target as HTMLElement, Component);
|
|
277
|
+
while (el && !el.hasClass("col-header")) {
|
|
278
|
+
el = el.parentElement();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return !!el;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// multisel, Ctrl + click: one row more, or one row less
|
|
285
|
+
const toggleRow = ( row: number ) => {
|
|
286
|
+
const id = this._dataview.getIdByIndex( row );
|
|
287
|
+
if( !this._selection.has( id ) ) {
|
|
288
|
+
this._addSelection( row );
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this._selection.delete( id );
|
|
293
|
+
this.queryAll(`.row[data-row="${row}"]`).forEach(el => {
|
|
294
|
+
el.removeClass("selected");
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
this.fire("selectionChange", { selection: this.getSelection( ) ?? [], empty: this._selection.size==0 } );
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// multisel, Shift + click: from the row that was selected last to this one
|
|
301
|
+
const selectTo = ( row: number ) => {
|
|
302
|
+
let last: any;
|
|
303
|
+
for (const id of this._selection) {
|
|
304
|
+
last = id;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const from = this._dataview.indexOfId( last );
|
|
308
|
+
|
|
309
|
+
this._clearSelection( false );
|
|
310
|
+
|
|
311
|
+
// the row to start from is added last: the next Shift + click starts from it too
|
|
312
|
+
if( from<0 ) {
|
|
313
|
+
this._addSelection( row, false );
|
|
314
|
+
}
|
|
315
|
+
else if( from<=row ) {
|
|
316
|
+
for( let r=row; r>=from; r-- ) {
|
|
317
|
+
this._addSelection( r, false );
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
for( let r=row; r<=from; r++ ) {
|
|
322
|
+
this._addSelection( r, false );
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.fire("selectionChange", { selection: this.getSelection( ), empty: false } );
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
// CLICK
|
|
331
|
+
this.addDOMEvent("click", (e) => {
|
|
332
|
+
const row = targetRow( e );
|
|
333
|
+
if (row!==undefined ) {
|
|
334
|
+
if( this.props.multisel && ( e.ctrlKey || e.metaKey ) ) {
|
|
335
|
+
toggleRow( row );
|
|
336
|
+
}
|
|
337
|
+
else if( this.props.multisel && e.shiftKey && this._selection.size ) {
|
|
338
|
+
selectTo( row );
|
|
339
|
+
}
|
|
340
|
+
// this row alone
|
|
341
|
+
else if( this._selection.size!=1 || !this._selection.has( this._dataview.getIdByIndex( row ) ) ) {
|
|
342
|
+
this._clearSelection( false );
|
|
343
|
+
this._addSelection(row);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// once the selection is what the click made it
|
|
347
|
+
const rec = this._dataview.getByIndex( row );
|
|
348
|
+
this.fire( "click", { context: rec } );
|
|
349
|
+
}
|
|
350
|
+
// a click in a column header sorts or sizes it: what is selected stays
|
|
351
|
+
else if( !inColHeader( e ) ) {
|
|
352
|
+
this._clearSelection( true );
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// DBLCLICK
|
|
357
|
+
this.addDOMEvent("dblclick", (e) => {
|
|
358
|
+
const row = targetRow( e );
|
|
359
|
+
if (row!==undefined ) {
|
|
360
|
+
if( !this._selection.has( this._dataview.getIdByIndex( row ) ) ) {
|
|
361
|
+
this._clearSelection( false );
|
|
362
|
+
this._addSelection(row);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
this._on_dblclk( e, row );
|
|
366
|
+
|
|
367
|
+
const rec = this._dataview.getByIndex( row );
|
|
368
|
+
this.fire( "dblClick", { context: rec } );
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// CONTEXT
|
|
373
|
+
this.addDOMEvent("contextmenu", (e) => {
|
|
374
|
+
const row = targetRow( e );
|
|
375
|
+
if (row!==undefined ) {
|
|
376
|
+
// on a selected row, the menu is for all that is selected
|
|
377
|
+
if( !this._selection.has( this._dataview.getIdByIndex( row ) ) ) {
|
|
378
|
+
this._clearSelection( false );
|
|
379
|
+
this._addSelection(row);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const rec = this._dataview.getByIndex( row );
|
|
383
|
+
this.fire( "contextMenu", { uievent: e, context: rec } );
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
e.preventDefault( );
|
|
387
|
+
e.stopPropagation( );
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// MOUSE OVER
|
|
391
|
+
this.addDOMEvent("mouseover", (e) => {
|
|
392
|
+
|
|
393
|
+
if (!this._has_fixed) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
let el = Component.parentElement(e.target as HTMLElement, Component);
|
|
398
|
+
while (el && !el.hasClass("row")) {
|
|
399
|
+
el = el.parentElement();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (el) {
|
|
403
|
+
const data = el.getData("row");
|
|
404
|
+
|
|
405
|
+
this.queryAll(".hover").forEach(x => x.removeClass("hover"));
|
|
406
|
+
|
|
407
|
+
if (data) {
|
|
408
|
+
const rows = this.queryAll(`.row[data-row="${data}"]`);
|
|
409
|
+
rows.forEach(x => x.addClass("hover"));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// MOUSE LEAVE
|
|
415
|
+
this.addDOMEvent("mouseleave", (e) => {
|
|
416
|
+
|
|
417
|
+
if (!this._has_fixed) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
this.queryAll(".hover").forEach(x => x.removeClass("hover"));
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
if( props.multisel ) {
|
|
425
|
+
this.addShortcut( "Mod+A", ( ) => this.selectAll( ) );
|
|
426
|
+
}
|
|
427
|
+
|
|
213
428
|
if (props.store) {
|
|
214
429
|
this.setStore(props.store);
|
|
215
430
|
|
|
@@ -272,7 +487,18 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
272
487
|
*/
|
|
273
488
|
|
|
274
489
|
navigate( sens: kbNav ) {
|
|
275
|
-
|
|
490
|
+
// no row to go to
|
|
491
|
+
if( !this._dataview || !this._dataview.getCount( ) ) {
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// the row to start from: the one of the first selected record
|
|
496
|
+
let fsel = -1;
|
|
497
|
+
if( this._selection.size ) {
|
|
498
|
+
fsel = this._dataview.indexOfId( this._selection.values().next().value );
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if( fsel<0 ) {
|
|
276
502
|
if( sens==kbNav.next || sens==kbNav.pgdn ) {
|
|
277
503
|
sens = kbNav.first;
|
|
278
504
|
}
|
|
@@ -289,7 +515,6 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
289
515
|
return true;
|
|
290
516
|
}
|
|
291
517
|
else if( sens==kbNav.prev || sens==kbNav.next ) {
|
|
292
|
-
const fsel = this._selection.values().next().value;
|
|
293
518
|
let nel = sens==kbNav.next ? fsel+1 : fsel-1;
|
|
294
519
|
if( nel>=0 && nel<this._dataview.getCount() ) {
|
|
295
520
|
this._clearSelection( false );
|
|
@@ -301,8 +526,6 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
301
526
|
else if( sens==kbNav.pgdn || sens==kbNav.pgup ) {
|
|
302
527
|
const pgh = this._vis_rows.size;
|
|
303
528
|
|
|
304
|
-
const fsel = this._selection.values().next().value;
|
|
305
|
-
|
|
306
529
|
let sby = sens==kbNav.pgdn ? pgh : -pgh;
|
|
307
530
|
let nel = fsel+sby;
|
|
308
531
|
|
|
@@ -366,8 +589,20 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
366
589
|
return;
|
|
367
590
|
}
|
|
368
591
|
|
|
369
|
-
|
|
370
|
-
|
|
592
|
+
// the data or the filter changed: a record that left the view leaves the selection
|
|
593
|
+
if (ev.change_type != 'sort') {
|
|
594
|
+
let lost = false;
|
|
595
|
+
|
|
596
|
+
for (const id of this._selection) {
|
|
597
|
+
if (this._dataview.indexOfId( id ) < 0) {
|
|
598
|
+
this._selection.delete( id );
|
|
599
|
+
lost = true;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (lost) {
|
|
604
|
+
this.fire("selectionChange", { selection: this.getSelection( ) ?? [], empty: this._selection.size==0 } );
|
|
605
|
+
}
|
|
371
606
|
}
|
|
372
607
|
|
|
373
608
|
this._updateFlexs( );
|
|
@@ -375,9 +610,9 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
375
610
|
this._update(true);
|
|
376
611
|
}
|
|
377
612
|
|
|
378
|
-
//
|
|
613
|
+
// the previous view stops following its store
|
|
379
614
|
if (this._dataview) {
|
|
380
|
-
this._dataview.
|
|
615
|
+
this._dataview.close( );
|
|
381
616
|
}
|
|
382
617
|
|
|
383
618
|
if (store) {
|
|
@@ -464,7 +699,8 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
464
699
|
if (lock) {
|
|
465
700
|
this._lock++;
|
|
466
701
|
}
|
|
467
|
-
|
|
702
|
+
// an unlock too many leaves it at 0: the grid goes on drawing
|
|
703
|
+
else if (this._lock > 0) {
|
|
468
704
|
if (--this._lock == 0 && this._dirty) {
|
|
469
705
|
this._update( true );
|
|
470
706
|
}
|
|
@@ -497,7 +733,9 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
497
733
|
const sizer = new CSizer("right");
|
|
498
734
|
|
|
499
735
|
sizer.on("stop", ( ) => {
|
|
736
|
+
// the flex columns took what was left: the body gets the width of its columns
|
|
500
737
|
this._updateFlexs( );
|
|
738
|
+
this._computeFullSize( );
|
|
501
739
|
})
|
|
502
740
|
|
|
503
741
|
sizer.on("resize", (ev) => {
|
|
@@ -519,6 +757,24 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
519
757
|
}
|
|
520
758
|
})
|
|
521
759
|
|
|
760
|
+
// a double click on the sizer: the column takes its share of the place
|
|
761
|
+
// that is left (flex 1). To stop taking it, the sizer is dragged
|
|
762
|
+
sizer.addDOMEvent("dblclick", (ev) => {
|
|
763
|
+
// both: the cell under the sizer sorts its column on a double click
|
|
764
|
+
ev.stopPropagation( );
|
|
765
|
+
ev.preventDefault( );
|
|
766
|
+
|
|
767
|
+
// a fixed column is not in the place that is shared
|
|
768
|
+
if (fixed || cdata.flex) {
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
cdata.flex = 1;
|
|
773
|
+
|
|
774
|
+
this._updateFlexs( );
|
|
775
|
+
this._computeFullSize( );
|
|
776
|
+
});
|
|
777
|
+
|
|
522
778
|
const cell = new Component({
|
|
523
779
|
cls: `cell`,
|
|
524
780
|
attrs: { "data-col": col },
|
|
@@ -530,6 +786,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
530
786
|
]
|
|
531
787
|
});
|
|
532
788
|
|
|
789
|
+
// the column the grid is sorted by
|
|
790
|
+
if (cdata.sens) {
|
|
791
|
+
cell.addClass( cdata.sens==="up" ? "sorted" : "sorted desc" );
|
|
792
|
+
}
|
|
793
|
+
|
|
533
794
|
cell.addDOMEvent("touchend", () => {
|
|
534
795
|
const last = cell.getInternalData("touchend");
|
|
535
796
|
const now = Date.now();
|
|
@@ -614,34 +875,29 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
614
875
|
this.setTimeout("sort", 50, () => {
|
|
615
876
|
let asc = true;
|
|
616
877
|
|
|
617
|
-
|
|
618
|
-
const scol = this.query(`.col-header .cell[data-col="${col}"]`);
|
|
619
|
-
if( !scol ) {
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
878
|
+
const cdata = this._getCol(col);
|
|
622
879
|
|
|
623
880
|
if( ascending===undefined ) {
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
asc = true;
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
asc = false;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
else {
|
|
633
|
-
const sorted = this.queryAll(".sorted");
|
|
634
|
-
sorted.forEach(x => x.removeClass("sorted asc desc"));
|
|
635
|
-
}
|
|
881
|
+
// a double click on the sorted column reverses it
|
|
882
|
+
asc = cdata.sens!=="up";
|
|
636
883
|
}
|
|
637
884
|
else {
|
|
638
885
|
asc = ascending;
|
|
639
886
|
}
|
|
640
887
|
|
|
641
|
-
|
|
642
|
-
|
|
888
|
+
// one column is sorted. It is kept in the columns, not in the header: the header
|
|
889
|
+
// may not be built yet, and is built again each time the grid comes in the document
|
|
890
|
+
this._columns.forEach( c => { c.sens = undefined; } );
|
|
891
|
+
cdata.sens = asc ? "up" : "dn";
|
|
643
892
|
|
|
644
|
-
const
|
|
893
|
+
const sorted = this.queryAll(".sorted");
|
|
894
|
+
sorted.forEach(x => x.removeClass("sorted asc desc"));
|
|
895
|
+
|
|
896
|
+
const scol = this.query(`.col-header .cell[data-col="${col}"]`);
|
|
897
|
+
if( scol ) {
|
|
898
|
+
scol.setClass("sorted");
|
|
899
|
+
scol.setClass("desc", !asc);
|
|
900
|
+
}
|
|
645
901
|
|
|
646
902
|
let num = false;
|
|
647
903
|
switch (cdata.type) {
|
|
@@ -664,6 +920,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
664
920
|
|
|
665
921
|
this._update(true);
|
|
666
922
|
|
|
923
|
+
// sorted by a double click on a header: what is selected is shown where it went
|
|
924
|
+
if( ascending===undefined && this._selection.size ) {
|
|
925
|
+
this._scrollToIndex( this._dataview.indexOfId( this._selection.values().next().value ) );
|
|
926
|
+
}
|
|
927
|
+
|
|
667
928
|
setWaitCursor(false);
|
|
668
929
|
});
|
|
669
930
|
}
|
|
@@ -689,6 +950,11 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
689
950
|
const col = column.id;
|
|
690
951
|
const type = column.type;
|
|
691
952
|
|
|
953
|
+
// a column that is not a field of the model (actions...) has only its renderer to draw it
|
|
954
|
+
if( column.renderer && isString( col ) && this._datamodel.getFieldIndex( col )<0 ) {
|
|
955
|
+
return column.renderer( rec, col );
|
|
956
|
+
}
|
|
957
|
+
|
|
692
958
|
let data = this._datamodel.getRaw(col, rec);
|
|
693
959
|
if (data === undefined || data === null) {
|
|
694
960
|
return null;
|
|
@@ -837,7 +1103,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
837
1103
|
const rowel = new Box({ cls: "row", style: { top: top.toFixed(2) + "px" }, content: els });
|
|
838
1104
|
rowel.setData("row", rowid + "");
|
|
839
1105
|
|
|
840
|
-
if (this._selection.has(
|
|
1106
|
+
if (this._selection.has( this._dataview.getRecId( rec ) )) {
|
|
841
1107
|
rowel.addClass("selected");
|
|
842
1108
|
}
|
|
843
1109
|
|
|
@@ -894,7 +1160,7 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
894
1160
|
const rowel = new Box({ cls: "row", style: { top: top + "px" }, content: cols });
|
|
895
1161
|
rowel.setData("row", rowid + "");
|
|
896
1162
|
|
|
897
|
-
if (this._selection.has(
|
|
1163
|
+
if (this._selection.has( this._dataview.getRecId( rec ) )) {
|
|
898
1164
|
rowel.addClass("selected");
|
|
899
1165
|
}
|
|
900
1166
|
|
|
@@ -913,8 +1179,13 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
913
1179
|
|
|
914
1180
|
for (let x = 0; x < ccount; x++) {
|
|
915
1181
|
const cdata = this._getCol(x);
|
|
916
|
-
|
|
917
|
-
|
|
1182
|
+
|
|
1183
|
+
// the fixed columns are not in the viewport: their width is not taken from its own
|
|
1184
|
+
if( cdata.fixed ) {
|
|
1185
|
+
continue;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
if( cdata.flex ) {
|
|
918
1189
|
flexc += cdata.flex;
|
|
919
1190
|
}
|
|
920
1191
|
else {
|
|
@@ -1016,129 +1287,6 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
1016
1287
|
this._update()
|
|
1017
1288
|
});
|
|
1018
1289
|
|
|
1019
|
-
// WHEEL
|
|
1020
|
-
this.addDOMEvent("wheel", (ev: WheelEvent) => {
|
|
1021
|
-
if (ev.deltaY && this._dataview && this._dataview.getCount() >= SCROLL_LIMIT) {
|
|
1022
|
-
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -1 : 1);
|
|
1023
|
-
ev.stopPropagation();
|
|
1024
|
-
ev.preventDefault();
|
|
1025
|
-
}
|
|
1026
|
-
|
|
1027
|
-
if (this._has_fixed && ev.deltaY) {
|
|
1028
|
-
// wheel on fixed part
|
|
1029
|
-
// fixed part do not have scrollbar, so we need to handle it by hand
|
|
1030
|
-
let t = ev.target as Node;
|
|
1031
|
-
while (t != this.dom) {
|
|
1032
|
-
if (t == this._vheader.dom) {
|
|
1033
|
-
this._viewport.dom.scrollBy(0, ev.deltaY < 0 ? -this._row_height : this._row_height);
|
|
1034
|
-
ev.stopPropagation();
|
|
1035
|
-
ev.preventDefault();
|
|
1036
|
-
break;
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
t = t.parentNode;
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
})
|
|
1043
|
-
|
|
1044
|
-
const targetRow = ( e: MouseEvent ) => {
|
|
1045
|
-
let el = Component.parentElement(e.target as HTMLElement, Component);
|
|
1046
|
-
while (el && !el.hasClass("row")) {
|
|
1047
|
-
el = el.parentElement();
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
if (el) {
|
|
1051
|
-
return el.getIntData("row");
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
return undefined;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
// CLICK
|
|
1059
|
-
this.addDOMEvent("click", (e) => {
|
|
1060
|
-
const row = targetRow( e );
|
|
1061
|
-
if (row!==undefined ) {
|
|
1062
|
-
//TODO: multiselection
|
|
1063
|
-
if( !this._selection.has(row) ) {
|
|
1064
|
-
this._clearSelection( false );
|
|
1065
|
-
this._addSelection(row);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
else {
|
|
1069
|
-
this._clearSelection( true );
|
|
1070
|
-
}
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
// DBLCLICK
|
|
1074
|
-
this.addDOMEvent("dblclick", (e) => {
|
|
1075
|
-
const row = targetRow( e );
|
|
1076
|
-
if (row!==undefined ) {
|
|
1077
|
-
//TODO: multiselection
|
|
1078
|
-
if( !this._selection.has(row) ) {
|
|
1079
|
-
this._clearSelection( false );
|
|
1080
|
-
this._addSelection(row);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
this._on_dblclk( e, row );
|
|
1084
|
-
|
|
1085
|
-
const rec = this._dataview.getByIndex( row );
|
|
1086
|
-
this.fire( "dblClick", { context: rec } );
|
|
1087
|
-
}
|
|
1088
|
-
});
|
|
1089
|
-
|
|
1090
|
-
// CONTEXT
|
|
1091
|
-
this.addDOMEvent("contextmenu", (e) => {
|
|
1092
|
-
const row = targetRow( e );
|
|
1093
|
-
if (row!==undefined ) {
|
|
1094
|
-
//TODO: multiselection
|
|
1095
|
-
if( !this._selection.has(row) ) {
|
|
1096
|
-
this._clearSelection( false );
|
|
1097
|
-
this._addSelection(row);
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
const rec = this._dataview.getByIndex( row );
|
|
1101
|
-
this.fire( "contextMenu", { uievent: e, context: rec } );
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
e.preventDefault( );
|
|
1105
|
-
e.stopPropagation( );
|
|
1106
|
-
});
|
|
1107
|
-
|
|
1108
|
-
// MOUSE OVER
|
|
1109
|
-
this.addDOMEvent("mouseover", (e) => {
|
|
1110
|
-
|
|
1111
|
-
if (!this._has_fixed) {
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
let el = Component.parentElement(e.target as HTMLElement, Component);
|
|
1116
|
-
while (el && !el.hasClass("row")) {
|
|
1117
|
-
el = el.parentElement();
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
if (el) {
|
|
1121
|
-
const data = el.getData("row");
|
|
1122
|
-
|
|
1123
|
-
this.queryAll(".hover").forEach(x => x.removeClass("hover"));
|
|
1124
|
-
|
|
1125
|
-
if (data) {
|
|
1126
|
-
const rows = this.queryAll(`.row[data-row="${data}"]`);
|
|
1127
|
-
rows.forEach(x => x.addClass("hover"));
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
|
|
1132
|
-
// MOUSE LEAVE
|
|
1133
|
-
this.addDOMEvent("mouseleave", (e) => {
|
|
1134
|
-
|
|
1135
|
-
if (!this._has_fixed) {
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1138
|
-
|
|
1139
|
-
this.queryAll(".hover").forEach(x => x.removeClass("hover"));
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
1290
|
this._updateFlexs( );
|
|
1143
1291
|
|
|
1144
1292
|
this._fheader = this._buildColHeader(true);
|
|
@@ -1271,17 +1419,37 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
1271
1419
|
this._clearSelection( false );
|
|
1272
1420
|
}
|
|
1273
1421
|
|
|
1422
|
+
/**
|
|
1423
|
+
* Select every record of the view (with multisel only).
|
|
1424
|
+
* The id of each record is kept, and selectionChange gives them all:
|
|
1425
|
+
* on a very large view this costs memory.
|
|
1426
|
+
*/
|
|
1427
|
+
|
|
1428
|
+
selectAll( ) {
|
|
1429
|
+
const count = this._dataview ? this._dataview.getCount( ) : 0;
|
|
1430
|
+
if( !this.props.multisel || !count ) {
|
|
1431
|
+
return;
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
for( let row=0; row<count; row++ ) {
|
|
1435
|
+
this._selection.add( this._dataview.getIdByIndex( row ) );
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
this.queryAll(".row").forEach(el => {
|
|
1439
|
+
el.addClass("selected");
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
this.fire("selectionChange", { selection: this.getSelection( ), empty: false } );
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1274
1445
|
private _clearSelection( notify: boolean ) {
|
|
1275
1446
|
if( !this._selection.size ) {
|
|
1276
1447
|
return;
|
|
1277
1448
|
}
|
|
1278
1449
|
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
el.removeClass("selected");
|
|
1283
|
-
})
|
|
1284
|
-
}
|
|
1450
|
+
this.queryAll(".row.selected").forEach(el => {
|
|
1451
|
+
el.removeClass("selected");
|
|
1452
|
+
});
|
|
1285
1453
|
|
|
1286
1454
|
this._selection.clear();
|
|
1287
1455
|
if( notify ) {
|
|
@@ -1293,16 +1461,18 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
1293
1461
|
*
|
|
1294
1462
|
*/
|
|
1295
1463
|
|
|
1296
|
-
private _addSelection(rowid: number) {
|
|
1297
|
-
this._selection.add(rowid)
|
|
1464
|
+
private _addSelection(rowid: number, notify = true) {
|
|
1465
|
+
this._selection.add( this._dataview.getIdByIndex( rowid ) );
|
|
1298
1466
|
|
|
1299
1467
|
const els = this.queryAll(`.row[data-row="${rowid}"]`)
|
|
1300
1468
|
els.forEach(el => {
|
|
1301
1469
|
el.addClass("selected");
|
|
1302
1470
|
});
|
|
1303
1471
|
|
|
1304
|
-
|
|
1305
|
-
|
|
1472
|
+
// all that is selected, not this row only
|
|
1473
|
+
if( notify ) {
|
|
1474
|
+
this.fire("selectionChange", { selection: this.getSelection( ), empty: false } );
|
|
1475
|
+
}
|
|
1306
1476
|
}
|
|
1307
1477
|
|
|
1308
1478
|
/**
|
|
@@ -1310,25 +1480,31 @@ export class Gridview<P extends GridviewProps = GridviewProps, E extends Gridvie
|
|
|
1310
1480
|
*/
|
|
1311
1481
|
|
|
1312
1482
|
getSelection( ) {
|
|
1313
|
-
|
|
1314
|
-
|
|
1483
|
+
// a record may be gone: out of the document, the grid does not follow its store
|
|
1484
|
+
const recs: DataRecord[] = [];
|
|
1485
|
+
for (const id of this._selection) {
|
|
1486
|
+
const rec = this._dataview.getById( id );
|
|
1487
|
+
if (rec) {
|
|
1488
|
+
recs.push( rec );
|
|
1489
|
+
}
|
|
1315
1490
|
}
|
|
1316
1491
|
|
|
1317
|
-
|
|
1318
|
-
return ids.map( id => this._dataview.getByIndex(id) );
|
|
1492
|
+
return recs.length ? recs : null;
|
|
1319
1493
|
}
|
|
1320
1494
|
|
|
1321
1495
|
/**
|
|
1322
|
-
*
|
|
1496
|
+
*
|
|
1323
1497
|
*/
|
|
1324
1498
|
|
|
1325
1499
|
getFirstSel( ) {
|
|
1326
|
-
|
|
1327
|
-
|
|
1500
|
+
for (const id of this._selection) {
|
|
1501
|
+
const rec = this._dataview.getById( id );
|
|
1502
|
+
if (rec) {
|
|
1503
|
+
return rec;
|
|
1504
|
+
}
|
|
1328
1505
|
}
|
|
1329
1506
|
|
|
1330
|
-
|
|
1331
|
-
return this._dataview.getByIndex( id );
|
|
1507
|
+
return null;
|
|
1332
1508
|
}
|
|
1333
1509
|
|
|
1334
1510
|
/**
|