@vaadin/crud 24.6.0-alpha9 → 24.6.0-rc1

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.
@@ -13,17 +13,12 @@ import '@vaadin/confirm-dialog/src/vaadin-confirm-dialog.js';
13
13
  import './vaadin-crud-dialog.js';
14
14
  import './vaadin-crud-grid.js';
15
15
  import './vaadin-crud-form.js';
16
- import { afterNextRender } from '@polymer/polymer/lib/utils/render-status.js';
17
16
  import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
18
- import { FocusRestorationController } from '@vaadin/a11y-base/src/focus-restoration-controller.js';
19
17
  import { ControllerMixin } from '@vaadin/component-base/src/controller-mixin.js';
20
18
  import { defineCustomElement } from '@vaadin/component-base/src/define.js';
21
19
  import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
22
- import { MediaQueryController } from '@vaadin/component-base/src/media-query-controller.js';
23
- import { SlotController } from '@vaadin/component-base/src/slot-controller.js';
24
20
  import { registerStyles, ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
25
- import { ButtonSlotController, FormSlotController, GridSlotController } from './vaadin-crud-controllers.js';
26
- import { getProperty, setProperty } from './vaadin-crud-helpers.js';
21
+ import { CrudMixin } from './vaadin-crud-mixin.js';
27
22
  import { crudStyles } from './vaadin-crud-styles.js';
28
23
 
29
24
  registerStyles('vaadin-crud', crudStyles, { moduleId: 'vaadin-crud-styles' });
@@ -175,8 +170,9 @@ registerStyles('vaadin-crud', crudStyles, { moduleId: 'vaadin-crud-styles' });
175
170
  * @mixes ControllerMixin
176
171
  * @mixes ElementMixin
177
172
  * @mixes ThemableMixin
173
+ * @mixes CrudMixin
178
174
  */
179
- class Crud extends ControllerMixin(ElementMixin(ThemableMixin(PolymerElement))) {
175
+ class Crud extends CrudMixin(ControllerMixin(ElementMixin(ThemableMixin(PolymerElement)))) {
180
176
  static get template() {
181
177
  return html`
182
178
  <div id="container">
@@ -255,1032 +251,6 @@ class Crud extends ControllerMixin(ElementMixin(ThemableMixin(PolymerElement)))
255
251
  static get cvdlName() {
256
252
  return 'vaadin-crud';
257
253
  }
258
-
259
- static get properties() {
260
- return {
261
- /**
262
- * A reference to the grid used for displaying the item list
263
- * @private
264
- */
265
- _grid: {
266
- type: Object,
267
- observer: '__gridChanged',
268
- },
269
-
270
- /**
271
- * A reference to the editor component which will be teleported to the dialog
272
- * @private
273
- */
274
- _form: {
275
- type: Object,
276
- observer: '__formChanged',
277
- },
278
-
279
- /**
280
- * A reference to the save button which will be teleported to the dialog
281
- * @private
282
- */
283
- _saveButton: {
284
- type: Object,
285
- },
286
-
287
- /**
288
- * A reference to the delete button which will be teleported to the dialog
289
- * @private
290
- */
291
- _deleteButton: {
292
- type: Object,
293
- },
294
-
295
- /**
296
- * A reference to the cancel button which will be teleported to the dialog
297
- * @private
298
- */
299
- _cancelButton: {
300
- type: Object,
301
- },
302
-
303
- /**
304
- * A reference to the default editor header element created by the CRUD
305
- * @private
306
- */
307
- _defaultHeader: {
308
- type: Object,
309
- },
310
-
311
- /**
312
- * A reference to the "New item" button
313
- * @private
314
- */
315
- _newButton: {
316
- type: Object,
317
- },
318
-
319
- /**
320
- * An array containing the items which will be stamped to the column template instances.
321
- * @type {Array<unknown> | undefined}
322
- */
323
- items: {
324
- type: Array,
325
- notify: true,
326
- observer: '__itemsChanged',
327
- },
328
-
329
- /**
330
- * The item being edited in the dialog.
331
- * @type {unknown}
332
- */
333
- editedItem: {
334
- type: Object,
335
- observer: '__editedItemChanged',
336
- notify: true,
337
- },
338
-
339
- /**
340
- * Sets how editor will be presented on desktop screen.
341
- *
342
- * Accepted values are:
343
- * - `` (default) - form will open as overlay
344
- * - `bottom` - form will open below the grid
345
- * - `aside` - form will open on the grid side (_right_, if lft and _left_ if rtl)
346
- * @attr {bottom|aside} editor-position
347
- * @type {!CrudEditorPosition}
348
- */
349
- editorPosition: {
350
- type: String,
351
- value: '',
352
- reflectToAttribute: true,
353
- observer: '__editorPositionChanged',
354
- },
355
-
356
- /**
357
- * Enables user to click on row to edit it.
358
- * Note: When enabled, auto-generated grid won't show the edit column.
359
- * @attr {boolean} edit-on-click
360
- * @type {boolean}
361
- */
362
- editOnClick: {
363
- type: Boolean,
364
- value: false,
365
- },
366
-
367
- /**
368
- * Function that provides items lazily. Receives arguments `params`, `callback`
369
- *
370
- * `params.page` Requested page index
371
- * `params.pageSize` Current page size
372
- * `params.filters` Currently applied filters
373
- * `params.sortOrders` Currently applied sorting orders
374
- *
375
- * `callback(items, size)` Callback function with arguments:
376
- * - `items` Current page of items
377
- * - `size` Total number of items
378
- * @type {CrudDataProvider | undefined}
379
- */
380
- dataProvider: {
381
- type: Function,
382
- observer: '__dataProviderChanged',
383
- },
384
-
385
- /**
386
- * Disable filtering when grid is autoconfigured.
387
- * @attr {boolean} no-filter
388
- */
389
- noFilter: Boolean,
390
-
391
- /**
392
- * Disable sorting when grid is autoconfigured.
393
- * @attr {boolean} no-sort
394
- */
395
- noSort: Boolean,
396
-
397
- /**
398
- * Remove grid headers when it is autoconfigured.
399
- * @attr {boolean} no-head
400
- */
401
- noHead: Boolean,
402
-
403
- /**
404
- * A comma-separated list of fields to include in the generated grid and the generated editor.
405
- *
406
- * It can be used to explicitly define the field order.
407
- *
408
- * When it is defined [`exclude`](#/elements/vaadin-crud#property-exclude) is ignored.
409
- *
410
- * Default is undefined meaning that all properties in the object should be mapped to fields.
411
- */
412
- include: String,
413
-
414
- /**
415
- * A comma-separated list of fields to be excluded from the generated grid and the generated editor.
416
- *
417
- * When [`include`](#/elements/vaadin-crud#property-include) is defined, this parameter is ignored.
418
- *
419
- * Default is to exclude all private fields (those properties starting with underscore)
420
- */
421
- exclude: String,
422
-
423
- /**
424
- * Reflects the opened status of the editor.
425
- */
426
- editorOpened: {
427
- type: Boolean,
428
- reflectToAttribute: true,
429
- notify: true,
430
- observer: '__editorOpenedChanged',
431
- },
432
-
433
- /**
434
- * Number of items in the data set which is reported by the grid.
435
- * Typically it reflects the number of filtered items displayed in the grid.
436
- *
437
- * Note: As with `<vaadin-grid>`, this property updates automatically only
438
- * if `items` is used for data.
439
- */
440
- size: {
441
- type: Number,
442
- readOnly: true,
443
- notify: true,
444
- },
445
-
446
- /**
447
- * Controls visibility state of toolbar.
448
- * When set to false toolbar is hidden and shown when set to true.
449
- * @attr {boolean} no-toolbar
450
- */
451
- noToolbar: {
452
- type: Boolean,
453
- value: false,
454
- reflectToAttribute: true,
455
- },
456
-
457
- /**
458
- * The object used to localize this component.
459
- * For changing the default localization, change the entire
460
- * _i18n_ object or just the property you want to modify.
461
- *
462
- * The object has the following JSON structure and default values:
463
- *
464
- * ```
465
- * {
466
- * newItem: 'New item',
467
- * editItem: 'Edit item',
468
- * saveItem: 'Save',
469
- * cancel: 'Cancel',
470
- * deleteItem: 'Delete...',
471
- * editLabel: 'Edit',
472
- * confirm: {
473
- * delete: {
474
- * title: 'Confirm delete',
475
- * content: 'Are you sure you want to delete the selected item? This action cannot be undone.',
476
- * button: {
477
- * confirm: 'Delete',
478
- * dismiss: 'Cancel'
479
- * }
480
- * },
481
- * cancel: {
482
- * title: 'Unsaved changes',
483
- * content: 'There are unsaved modifications to the item.',
484
- * button: {
485
- * confirm: 'Discard',
486
- * dismiss: 'Continue editing'
487
- * }
488
- * }
489
- * }
490
- * }
491
- * ```
492
- *
493
- * @type {!CrudI18n}
494
- * @default {English/US}
495
- */
496
- i18n: {
497
- type: Object,
498
- value() {
499
- return {
500
- newItem: 'New item',
501
- editItem: 'Edit item',
502
- saveItem: 'Save',
503
- cancel: 'Cancel',
504
- deleteItem: 'Delete...',
505
- editLabel: 'Edit',
506
- confirm: {
507
- delete: {
508
- title: 'Delete item',
509
- content: 'Are you sure you want to delete this item? This action cannot be undone.',
510
- button: {
511
- confirm: 'Delete',
512
- dismiss: 'Cancel',
513
- },
514
- },
515
- cancel: {
516
- title: 'Discard changes',
517
- content: 'There are unsaved changes to this item.',
518
- button: {
519
- confirm: 'Discard',
520
- dismiss: 'Cancel',
521
- },
522
- },
523
- },
524
- };
525
- },
526
- },
527
-
528
- /** @private */
529
- __dialogAriaLabel: String,
530
-
531
- /** @private */
532
- __isDirty: Boolean,
533
-
534
- /** @private */
535
- __isNew: Boolean,
536
-
537
- /**
538
- * @type {boolean}
539
- * @protected
540
- */
541
- _fullscreen: {
542
- type: Boolean,
543
- observer: '__fullscreenChanged',
544
- },
545
-
546
- /**
547
- * @type {string}
548
- * @protected
549
- */
550
- _fullscreenMediaQuery: {
551
- value: '(max-width: 600px), (max-height: 600px)',
552
- },
553
- };
554
- }
555
-
556
- static get observers() {
557
- return [
558
- '__headerPropsChanged(_defaultHeader, __isNew, i18n.newItem, i18n.editItem)',
559
- '__formPropsChanged(_form, _theme, include, exclude)',
560
- '__gridPropsChanged(_grid, _theme, include, exclude, noFilter, noHead, noSort, items)',
561
- '__i18nChanged(i18n, _grid)',
562
- '__editOnClickChanged(editOnClick, _grid)',
563
- '__saveButtonPropsChanged(_saveButton, i18n.saveItem, __isDirty)',
564
- '__cancelButtonPropsChanged(_cancelButton, i18n.cancel)',
565
- '__deleteButtonPropsChanged(_deleteButton, i18n.deleteItem, __isNew)',
566
- '__newButtonPropsChanged(_newButton, i18n.newItem)',
567
- ];
568
- }
569
-
570
- /** @private */
571
- static _isValidEditorPosition(editorPosition) {
572
- return ['bottom', 'aside'].includes(editorPosition);
573
- }
574
-
575
- constructor() {
576
- super();
577
-
578
- this.__cancel = this.__cancel.bind(this);
579
- this.__delete = this.__delete.bind(this);
580
- this.__save = this.__save.bind(this);
581
- this.__new = this.__new.bind(this);
582
- this.__onFormChange = this.__onFormChange.bind(this);
583
- this.__onGridEdit = this.__onGridEdit.bind(this);
584
- this.__onGridSizeChanged = this.__onGridSizeChanged.bind(this);
585
- this.__onGridActiveItemChanged = this.__onGridActiveItemChanged.bind(this);
586
-
587
- this.__focusRestorationController = new FocusRestorationController();
588
- }
589
-
590
- /** @protected */
591
- get _headerNode() {
592
- return this._headerController && this._headerController.node;
593
- }
594
-
595
- /**
596
- * A reference to all fields inside the [`_form`](#/elements/vaadin-crud#property-_form) element
597
- * @return {!Array<!HTMLElement>}
598
- * @protected
599
- */
600
- get _fields() {
601
- if (!this.__fields || !this.__fields.length) {
602
- this.__fields = Array.from(this._form.querySelectorAll('*')).filter((e) => e.validate || e.checkValidity);
603
- }
604
- return this.__fields;
605
- }
606
-
607
- /** @protected */
608
- ready() {
609
- super.ready();
610
-
611
- this.$.dialog.$.overlay.addEventListener('vaadin-overlay-outside-click', this.__cancel);
612
- this.$.dialog.$.overlay.addEventListener('vaadin-overlay-escape-press', this.__cancel);
613
-
614
- this._headerController = new SlotController(this, 'header', 'h3', {
615
- initializer: (node) => {
616
- this._defaultHeader = node;
617
- },
618
- });
619
- this.addController(this._headerController);
620
-
621
- this._gridController = new GridSlotController(this);
622
- this.addController(this._gridController);
623
-
624
- this.addController(new FormSlotController(this));
625
-
626
- // Init controllers in `ready()` (not constructor) so that Flow can set `_noDefaultButtons`
627
- this._newButtonController = new ButtonSlotController(this, 'new', 'primary', this._noDefaultButtons);
628
- this._saveButtonController = new ButtonSlotController(this, 'save', 'primary', this._noDefaultButtons);
629
- this._cancelButtonController = new ButtonSlotController(this, 'cancel', 'tertiary', this._noDefaultButtons);
630
- this._deleteButtonController = new ButtonSlotController(this, 'delete', 'tertiary error', this._noDefaultButtons);
631
-
632
- this.addController(this._newButtonController);
633
-
634
- // NOTE: order in which buttons are added should match the order of slots in template
635
- this.addController(this._saveButtonController);
636
- this.addController(this._cancelButtonController);
637
- this.addController(this._deleteButtonController);
638
-
639
- this.addController(
640
- new MediaQueryController(this._fullscreenMediaQuery, (matches) => {
641
- this._fullscreen = matches;
642
- }),
643
- );
644
-
645
- this.addController(this.__focusRestorationController);
646
- }
647
-
648
- /**
649
- * @param {boolean} isDirty
650
- * @private
651
- */
652
- __isSaveBtnDisabled(isDirty) {
653
- // Used instead of isDirty property binding in order to enable overriding of the behavior
654
- // by overriding the method (i.e. from Flow component)
655
- return !isDirty;
656
- }
657
-
658
- /**
659
- * @param {HTMLElement | undefined} headerNode
660
- * @param {boolean} isNew
661
- * @param {string} i18nNewItem
662
- * @param {string} i18nEditItem
663
- * @private
664
- */
665
- __headerPropsChanged(headerNode, isNew, i18nNewItem, i18nEditItem) {
666
- if (headerNode) {
667
- headerNode.textContent = isNew ? i18nNewItem : i18nEditItem;
668
- }
669
- }
670
-
671
- /**
672
- * @param {CrudI18n} i18n
673
- * @param {CrudGrid | Grid} grid
674
- * @private
675
- */
676
- __i18nChanged(i18n, grid) {
677
- if (!grid) {
678
- return;
679
- }
680
-
681
- afterNextRender(grid, () => {
682
- Array.from(grid.querySelectorAll('vaadin-crud-edit-column')).forEach((column) => {
683
- column.ariaLabel = i18n.editLabel;
684
- });
685
- });
686
- }
687
-
688
- /** @private */
689
- __editorPositionChanged(editorPosition) {
690
- if (Crud._isValidEditorPosition(editorPosition)) {
691
- return;
692
- }
693
- this.editorPosition = '';
694
- }
695
-
696
- /** @private */
697
- __editorOpenedChanged(opened, oldOpened) {
698
- if (!opened && oldOpened) {
699
- this.__closeEditor();
700
- } else {
701
- this.__formChanged(this._form);
702
- }
703
-
704
- if (opened) {
705
- this.__ensureChildren();
706
-
707
- // When using bottom / aside editor position,
708
- // auto-focus the editor element on open.
709
- if (this._form.parentElement === this) {
710
- this.$.editor.setAttribute('tabindex', '0');
711
- this.$.editor.focus();
712
- } else {
713
- this.$.editor.removeAttribute('tabindex');
714
- }
715
- }
716
-
717
- this.__toggleToolbar();
718
-
719
- // Make sure to reset scroll position
720
- this.$.scroller.scrollTop = 0;
721
- }
722
-
723
- /** @private */
724
- __fullscreenChanged(fullscreen, oldFullscreen) {
725
- if (fullscreen || oldFullscreen) {
726
- this.__toggleToolbar();
727
-
728
- this.__ensureChildren();
729
-
730
- this.toggleAttribute('fullscreen', fullscreen);
731
- }
732
- }
733
-
734
- /** @private */
735
- __toggleToolbar() {
736
- // Hide toolbar to give more room for the editor when it's positioned below the grid
737
- if (this.editorPosition === 'bottom' && !this._fullscreen) {
738
- this.$.toolbar.style.display = this.editorOpened ? 'none' : '';
739
- }
740
- }
741
-
742
- /** @private */
743
- __moveChildNodes(target) {
744
- const nodes = [this._headerNode, this._form, this._saveButton, this._cancelButton, this._deleteButton];
745
- if (!nodes.every((node) => node instanceof HTMLElement)) {
746
- return;
747
- }
748
-
749
- // Teleport header node, form, and the buttons to corresponding slots.
750
- // NOTE: order in which buttons are moved matches the order of slots.
751
- nodes.forEach((node) => {
752
- target.appendChild(node);
753
- });
754
-
755
- // Wait to set label until slotted element has been moved.
756
- setTimeout(() => {
757
- this.__dialogAriaLabel = this._headerNode.textContent.trim();
758
- });
759
- }
760
-
761
- /** @private */
762
- __shouldOpenDialog(fullscreen, editorPosition) {
763
- return editorPosition === '' || fullscreen;
764
- }
765
-
766
- /** @private */
767
- __ensureChildren() {
768
- if (this.__shouldOpenDialog(this._fullscreen, this.editorPosition)) {
769
- // Move form to dialog
770
- this.__moveChildNodes(this.$.dialog.$.overlay);
771
- } else {
772
- // Move form to crud
773
- this.__moveChildNodes(this);
774
- }
775
- }
776
-
777
- /** @private */
778
- __computeDialogOpened(opened, fullscreen, editorPosition) {
779
- // Only open dialog when editorPosition is "" or fullscreen is set
780
- return this.__shouldOpenDialog(fullscreen, editorPosition) ? opened : false;
781
- }
782
-
783
- /** @private */
784
- __computeEditorHidden(opened, fullscreen, editorPosition) {
785
- // Only show editor when editorPosition is "bottom" or "aside"
786
- if (['aside', 'bottom'].includes(editorPosition) && !fullscreen) {
787
- return !opened;
788
- }
789
-
790
- return true;
791
- }
792
-
793
- /** @private */
794
- __onDialogOpened(event) {
795
- this.editorOpened = event.detail.value;
796
- }
797
-
798
- /** @private */
799
- __onGridEdit(event) {
800
- event.stopPropagation();
801
- this.__confirmBeforeChangingEditedItem(event.detail.item);
802
- }
803
-
804
- /** @private */
805
- __onFormChange() {
806
- this.__isDirty = true;
807
- }
808
-
809
- /** @private */
810
- __onGridSizeChanged() {
811
- this._setSize(this._grid.size);
812
- }
813
-
814
- /**
815
- * @param {CrudGrid | Grid} grid
816
- * @param {CrudGrid | Grid | undefined} oldGrid
817
- * @private
818
- */
819
- __gridChanged(grid, oldGrid) {
820
- if (oldGrid) {
821
- oldGrid.removeEventListener('edit', this.__onGridEdit);
822
- oldGrid.removeEventListener('size-changed', this.__onGridSizeChanged);
823
- }
824
- if (this.dataProvider) {
825
- this.__dataProviderChanged(this.dataProvider);
826
- }
827
- if (this.editedItem) {
828
- this.__editedItemChanged(this.editedItem);
829
- }
830
- grid.addEventListener('edit', this.__onGridEdit);
831
- grid.addEventListener('size-changed', this.__onGridSizeChanged);
832
- this.__onGridSizeChanged();
833
- }
834
-
835
- /**
836
- * @param {HTMLElement | undefined | null} form
837
- * @param {HTMLElement | undefined | null} oldForm
838
- * @private
839
- */
840
- __formChanged(form, oldForm) {
841
- if (oldForm && oldForm.parentElement) {
842
- oldForm.parentElement.removeChild(oldForm);
843
- oldForm.removeEventListener('change', this.__onFormChange);
844
- oldForm.removeEventListener('input', this.__onFormChange);
845
- }
846
- if (!form) {
847
- return;
848
- }
849
- if (this.items) {
850
- this.__itemsChanged(this.items);
851
- }
852
- if (this.editedItem) {
853
- this.__editedItemChanged(this.editedItem);
854
- }
855
- form.addEventListener('change', this.__onFormChange);
856
- form.addEventListener('input', this.__onFormChange);
857
- }
858
-
859
- /**
860
- * @param {HTMLElement | undefined} form
861
- * @param {string} theme
862
- * @param {string | string[] | undefined} include
863
- * @param {string | RegExp} exclude
864
- * @private
865
- */
866
- __formPropsChanged(form, theme, include, exclude) {
867
- if (form) {
868
- form.include = include;
869
- form.exclude = exclude;
870
-
871
- if (theme) {
872
- form.setAttribute('theme', theme);
873
- } else {
874
- form.removeAttribute('theme');
875
- }
876
- }
877
- }
878
-
879
- /**
880
- * @param {HTMLElement | undefined} grid
881
- * @param {string} theme
882
- * @param {string | string[] | undefined} include
883
- * @param {string | RegExp} exclude
884
- * @param {boolean} noFilter
885
- * @param {boolean} noHead
886
- * @param {boolean} noSort
887
- * @param {Array<unknown> | undefined} items
888
- * @private
889
- */
890
- // eslint-disable-next-line @typescript-eslint/max-params
891
- __gridPropsChanged(grid, theme, include, exclude, noFilter, noHead, noSort, items) {
892
- if (!grid) {
893
- return;
894
- }
895
-
896
- if (grid === this._gridController.defaultNode) {
897
- grid.noFilter = noFilter;
898
- grid.noHead = noHead;
899
- grid.noSort = noSort;
900
- grid.include = include;
901
- grid.exclude = exclude;
902
-
903
- if (theme) {
904
- grid.setAttribute('theme', theme);
905
- } else {
906
- grid.removeAttribute('theme');
907
- }
908
- }
909
-
910
- grid.items = items;
911
- }
912
-
913
- /**
914
- * @param {HTMLElement | undefined} saveButton
915
- * @param {string} i18nLabel
916
- * @param {boolean} isDirty
917
- * @private
918
- */
919
- __saveButtonPropsChanged(saveButton, i18nLabel, isDirty) {
920
- if (saveButton) {
921
- saveButton.toggleAttribute('disabled', this.__isSaveBtnDisabled(isDirty));
922
-
923
- if (saveButton === this._saveButtonController.defaultNode) {
924
- saveButton.textContent = i18nLabel;
925
- }
926
- }
927
- }
928
-
929
- /**
930
- * @param {HTMLElement | undefined} deleteButton
931
- * @param {string} i18nLabel
932
- * @param {boolean} isNew
933
- * @private
934
- */
935
- __deleteButtonPropsChanged(deleteButton, i18nLabel, isNew) {
936
- if (deleteButton) {
937
- deleteButton.toggleAttribute('hidden', isNew);
938
-
939
- if (deleteButton === this._deleteButtonController.defaultNode) {
940
- deleteButton.textContent = i18nLabel;
941
- }
942
- }
943
- }
944
-
945
- /**
946
- * @param {HTMLElement | undefined} cancelButton
947
- * @param {string} i18nLabel
948
- * @private
949
- */
950
- __cancelButtonPropsChanged(cancelButton, i18nLabel) {
951
- if (cancelButton && cancelButton === this._cancelButtonController.defaultNode) {
952
- cancelButton.textContent = i18nLabel;
953
- }
954
- }
955
-
956
- /**
957
- * @param {HTMLElement | undefined} newButton
958
- * @param {string} i18nNewItem
959
- * @private
960
- */
961
- __newButtonPropsChanged(newButton, i18nNewItem) {
962
- if (newButton && newButton === this._newButtonController.defaultNode) {
963
- newButton.textContent = i18nNewItem;
964
- }
965
- }
966
-
967
- /** @private */
968
- __dataProviderChanged(dataProvider) {
969
- if (this._grid) {
970
- this._grid.dataProvider = this.__createDataProviderProxy(dataProvider);
971
- }
972
- }
973
-
974
- /** @private */
975
- __editOnClickChanged(editOnClick, grid) {
976
- if (!grid) {
977
- return;
978
- }
979
-
980
- grid.hideEditColumn = editOnClick;
981
-
982
- if (editOnClick) {
983
- grid.addEventListener('active-item-changed', this.__onGridActiveItemChanged);
984
- } else {
985
- grid.removeEventListener('active-item-changed', this.__onGridActiveItemChanged);
986
- }
987
- }
988
-
989
- /** @private */
990
- __onGridActiveItemChanged(event) {
991
- const item = event.detail.value;
992
- if (this.editorOpened && this.__isDirty) {
993
- this.__confirmBeforeChangingEditedItem(item);
994
- return;
995
- }
996
- if (item) {
997
- this.__edit(item);
998
- } else if (!this.__keepOpened) {
999
- this.__closeEditor();
1000
- }
1001
- }
1002
-
1003
- /** @private */
1004
- __confirmBeforeChangingEditedItem(item, keepOpened) {
1005
- if (
1006
- this.editorOpened && // Editor opened
1007
- this.__isDirty && // Form change has been made
1008
- this.editedItem !== item // Item is different
1009
- ) {
1010
- this.$.confirmCancel.opened = true;
1011
- this.addEventListener(
1012
- 'cancel',
1013
- (event) => {
1014
- event.preventDefault(); // Prevent closing the editor
1015
- if (item || keepOpened) {
1016
- this.__edit(item);
1017
- this.__clearItemAndKeepEditorOpened(item, keepOpened);
1018
- } else {
1019
- this.__closeEditor();
1020
- }
1021
- },
1022
- { once: true },
1023
- );
1024
- } else {
1025
- this.__edit(item);
1026
- this.__clearItemAndKeepEditorOpened(item, keepOpened);
1027
- }
1028
- }
1029
-
1030
- /** @private */
1031
- __clearItemAndKeepEditorOpened(item, keepOpened) {
1032
- if (!item) {
1033
- setTimeout(() => {
1034
- this.__keepOpened = keepOpened;
1035
- this.editedItem = this._grid.activeItem = undefined;
1036
- });
1037
- }
1038
- }
1039
-
1040
- /** @private */
1041
- __createDataProviderProxy(dataProvider) {
1042
- return (params, callback) => {
1043
- const callbackProxy = (chunk, size) => {
1044
- if (chunk && chunk[0]) {
1045
- this.__model = chunk[0];
1046
- }
1047
-
1048
- callback(chunk, size);
1049
- };
1050
-
1051
- dataProvider(params, callbackProxy);
1052
- };
1053
- }
1054
-
1055
- /** @private */
1056
- __itemsChanged(items) {
1057
- if (this.items && this.items[0]) {
1058
- this.__model = items[0];
1059
- }
1060
- }
1061
-
1062
- /** @private */
1063
- __editedItemChanged(item) {
1064
- if (!this._form) {
1065
- return;
1066
- }
1067
- if (item) {
1068
- if (!this._fields.length && this._form._configure) {
1069
- if (this.__model) {
1070
- this._form._configure(this.__model);
1071
- } else {
1072
- console.warn(
1073
- '<vaadin-crud> Unable to autoconfigure form because the data structure is unknown. ' +
1074
- 'Either specify `include` or ensure at least one item is available beforehand.',
1075
- );
1076
- }
1077
- }
1078
- this._form.item = item;
1079
- this._fields.forEach((e) => {
1080
- const path = e.path || e.getAttribute('path');
1081
- if (path) {
1082
- e.value = getProperty(path, item);
1083
- }
1084
- });
1085
-
1086
- this.__isNew = !!(this.__isNew || (this.items && this.items.indexOf(item) < 0));
1087
- this.editorOpened = true;
1088
- }
1089
- }
1090
-
1091
- /** @private */
1092
- __validate() {
1093
- return this._fields.every((e) => (e.validate || e.checkValidity).call(e));
1094
- }
1095
-
1096
- /** @private */
1097
- __setHighlightedItem(item) {
1098
- if (this._grid === this._gridController.defaultNode) {
1099
- this._grid.selectedItems = item ? [item] : [];
1100
- }
1101
- }
1102
-
1103
- /** @private */
1104
- __closeEditor() {
1105
- this.editorOpened = false;
1106
- this.__isDirty = false;
1107
- this.__setHighlightedItem(null);
1108
-
1109
- // Delay changing the item in order not to modify editor while closing
1110
- setTimeout(() => this.__clearItemAndKeepEditorOpened(null, false));
1111
- }
1112
-
1113
- /** @private */
1114
- __new() {
1115
- this.__confirmBeforeChangingEditedItem(null, true);
1116
- }
1117
-
1118
- /** @private */
1119
- __edit(item) {
1120
- if (this.editedItem === item) {
1121
- return;
1122
- }
1123
- this.__setHighlightedItem(item);
1124
- this.__openEditor(item);
1125
- }
1126
-
1127
- /** @private */
1128
- __fireEvent(type, item) {
1129
- const event = new CustomEvent(type, { detail: { item }, cancelable: true });
1130
- this.dispatchEvent(event);
1131
- return event.defaultPrevented === false;
1132
- }
1133
-
1134
- /** @private */
1135
- __openEditor(item) {
1136
- this.__focusRestorationController.saveFocus();
1137
-
1138
- this.__isDirty = false;
1139
- this.__isNew = !item;
1140
- const result = this.__fireEvent(this.__isNew ? 'new' : 'edit', item);
1141
- if (result) {
1142
- this.editedItem = item || {};
1143
- } else {
1144
- this.editorOpened = true;
1145
- }
1146
- }
1147
-
1148
- /** @private */
1149
- __restoreFocusOnDelete() {
1150
- if (this._grid._flatSize === 1) {
1151
- this._newButton.focus();
1152
- } else {
1153
- this._grid._focusFirstVisibleRow();
1154
- }
1155
- }
1156
-
1157
- /** @private */
1158
- __restoreFocusOnSaveOrCancel() {
1159
- const focusNode = this.__focusRestorationController.focusNode;
1160
- const row = this._grid._getRowContainingNode(focusNode);
1161
- if (!row) {
1162
- this.__focusRestorationController.restoreFocus();
1163
- return;
1164
- }
1165
-
1166
- if (this._grid._isItemAssignedToRow(this.editedItem, row) && this._grid._isInViewport(row)) {
1167
- this.__focusRestorationController.restoreFocus();
1168
- } else {
1169
- this._grid._focusFirstVisibleRow();
1170
- }
1171
- }
1172
-
1173
- /** @private */
1174
- __save() {
1175
- if (!this.__validate()) {
1176
- return;
1177
- }
1178
-
1179
- const item = { ...this.editedItem };
1180
- this._fields.forEach((e) => {
1181
- const path = e.path || e.getAttribute('path');
1182
- if (path) {
1183
- setProperty(path, e.value, item);
1184
- }
1185
- });
1186
- const result = this.__fireEvent('save', item);
1187
- if (result) {
1188
- if (this.__isNew && !this.dataProvider) {
1189
- if (!this.items) {
1190
- this.items = [item];
1191
- } else {
1192
- this.items.push(item);
1193
- }
1194
- } else {
1195
- if (!this.editedItem) {
1196
- this.editedItem = {};
1197
- }
1198
- Object.assign(this.editedItem, item);
1199
- }
1200
-
1201
- this.__restoreFocusOnSaveOrCancel();
1202
- this._grid.clearCache();
1203
- this.__closeEditor();
1204
- }
1205
- }
1206
-
1207
- /** @private */
1208
- __cancel() {
1209
- if (this.__isDirty) {
1210
- this.$.confirmCancel.opened = true;
1211
- } else {
1212
- this.__confirmCancel();
1213
- }
1214
- }
1215
-
1216
- /** @private */
1217
- __confirmCancel() {
1218
- const result = this.__fireEvent('cancel', this.editedItem);
1219
- if (result) {
1220
- this.__restoreFocusOnSaveOrCancel();
1221
- this.__closeEditor();
1222
- }
1223
- }
1224
-
1225
- /** @private */
1226
- __delete() {
1227
- this.$.confirmDelete.opened = true;
1228
- }
1229
-
1230
- /** @private */
1231
- __confirmDelete() {
1232
- const result = this.__fireEvent('delete', this.editedItem);
1233
- if (result) {
1234
- if (this.items && this.items.indexOf(this.editedItem) >= 0) {
1235
- this.items.splice(this.items.indexOf(this.editedItem), 1);
1236
- }
1237
-
1238
- this.__restoreFocusOnDelete();
1239
- this._grid.clearCache();
1240
- this.__closeEditor();
1241
- }
1242
- }
1243
-
1244
- /**
1245
- * Fired when user wants to edit an existing item. If the default is prevented, then
1246
- * a new item is not assigned to the form, giving that responsibility to the app, though
1247
- * dialog is always opened.
1248
- *
1249
- * @event edit
1250
- * @param {Object} detail.item the item to edit
1251
- */
1252
-
1253
- /**
1254
- * Fired when user wants to create a new item.
1255
- *
1256
- * @event new
1257
- */
1258
-
1259
- /**
1260
- * Fired when user wants to delete item. If the default is prevented, then
1261
- * no action is performed, items array is not modified nor dialog closed
1262
- *
1263
- * @event delete
1264
- * @param {Object} detail.item the item to delete
1265
- */
1266
-
1267
- /**
1268
- * Fired when user discards edition. If the default is prevented, then
1269
- * no action is performed, user is responsible to close dialog and reset
1270
- * item and grid.
1271
- *
1272
- * @event cancel
1273
- * @param {Object} detail.item the item to delete
1274
- */
1275
-
1276
- /**
1277
- * Fired when user wants to save a new or an existing item. If the default is prevented, then
1278
- * no action is performed, items array is not modified nor dialog closed
1279
- *
1280
- * @event save
1281
- * @param {Object} detail.item the item to save
1282
- * @param {Object} detail.new whether the item is a new one
1283
- */
1284
254
  }
1285
255
 
1286
256
  defineCustomElement(Crud);