@dile/crud 1.8.0 → 1.9.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.
@@ -205,8 +205,8 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
205
205
  <dile-crud-list-item
206
206
  itemId="${this.computeItemId(element)}"
207
207
  .actionIds="${this.actionIds}"
208
- ?disableEdit=${this.config?.customization?.disableEdit}
209
- ?disableDelete=${this.config?.customization?.disableDelete}
208
+ ?disableEdit=${!this.isItemEditable(element)}
209
+ ?disableDelete=${!this.isItemDeletable(element)}
210
210
  ?disableRestore=${this.config?.customization?.disableRestore}
211
211
  ?hideCheckboxSelection="${this.config?.customization?.hideCheckboxSelection}"
212
212
  @item-checkbox-changed=${this.onItemsCheckboxChanged}
@@ -229,6 +229,18 @@ export class DileCrudList extends DileI18nMixin(DileLoading(LitElement)) {
229
229
  return this.config.computeItemId(element);
230
230
  }
231
231
 
232
+ isItemEditable(element) {
233
+ const isGloballyDisabled = this.config?.customization?.disableEdit;
234
+ const isItemEditable = this.config?.isItemEditable(element) ?? true;
235
+ return !isGloballyDisabled && isItemEditable;
236
+ }
237
+
238
+ isItemDeletable(element) {
239
+ const isGloballyDisabled = this.config?.customization?.disableDelete;
240
+ const isItemDeletable = this.config?.isItemDeletable(element) ?? true;
241
+ return !isGloballyDisabled && isItemDeletable;
242
+ }
243
+
232
244
  get allIdsUrl() {
233
245
  return `${this.config.endpoint}/allids`;
234
246
  }
@@ -0,0 +1 @@
1
+ export { DileManyRelation } from './src/DileManyRelation.js';
@@ -0,0 +1,3 @@
1
+ import { DileManyRelation } from './src/DileManyRelation.js';
2
+
3
+ customElements.define('dile-many-relation', DileManyRelation);
@@ -0,0 +1,283 @@
1
+ import { LitElement, html, css } from 'lit';
2
+ import { DileI18nMixin } from '../../../lib/DileI18nMixin.js';
3
+ import { addIcon, clearIcon } from '@dile/icons';
4
+ import '../../ajax/ajax.js';
5
+ import '../../ajax-select-crud/ajax-select-crud.js';
6
+ import '@dile/ui/components/icon/icon.js';
7
+ import '@dile/ui/components/spinner/spinner.js';
8
+
9
+ export class DileManyRelation extends DileI18nMixin(LitElement) {
10
+ static styles = [
11
+ css`
12
+ :host {
13
+ display: block;
14
+ }
15
+ dile-ajax-select-crud{
16
+ margin-bottom: 0;
17
+ }
18
+ .relation-item {
19
+ display: flex;
20
+ align-items: center;
21
+ gap: 0.5rem;
22
+ padding: 0.25rem 0;
23
+ }
24
+ .relation-item-content {
25
+ flex: 1;
26
+ padding-left: 0.4rem;
27
+ font-size: var(--many-relation-item-font-size, 0.9rem);
28
+ color: var(--many-relation-item-color, inherit);
29
+ }
30
+ button.remove-btn,
31
+ button.add-btn {
32
+ background: none;
33
+ border: none;
34
+ padding: 0;
35
+ cursor: pointer;
36
+ display: flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ width: var(--many-relation-add-size, 38px);
40
+ flex-shrink: 0;
41
+ }
42
+ button.remove-btn:disabled {
43
+ opacity: 0.4;
44
+ cursor: not-allowed;
45
+ }
46
+ button.remove-btn dile-icon {
47
+ --dile-icon-color: var(--many-relation-remove-color, #c00);
48
+ --dile-icon-size: var(--many-relation-remove-size, 22px);
49
+ }
50
+ .add-row {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 0.5rem;
54
+ margin-bottom: 0.5rem;
55
+ }
56
+ .add-row dile-ajax-select-crud {
57
+ flex: 1;
58
+ }
59
+ button.add-btn dile-icon {
60
+ --dile-icon-color: var(--many-relation-add-color, #2962ff);
61
+ --dile-icon-size: var(--many-relation-add-size, 38px);
62
+ margin-bottom: 8px;
63
+ }
64
+ button.add-btn:disabled dile-icon {
65
+ --dile-icon-color: var(--many-relation-add-disabled-color, #ccc);
66
+ }
67
+ button.add-btn:disabled {
68
+ cursor: default;
69
+ }
70
+ .empty-msg {
71
+ color: var(--many-relation-empty-color, #888);
72
+ font-style: var(--many-relation-empty-font-style, italic);
73
+ font-size: var(--many-relation-empty-font-size, 0.9rem);
74
+ }
75
+ `
76
+ ];
77
+
78
+ static get properties() {
79
+ return {
80
+ // Endpoints
81
+ endpointGet: { type: String },
82
+ endpointList: { type: String },
83
+ endpointAdd: { type: String },
84
+ endpointRemove: { type: String },
85
+
86
+ // Initial data behaviour
87
+ relatedItems: { type: Array },
88
+ loadFromEndpoint: { type: Boolean },
89
+
90
+ // Select (dile-ajax-select-crud) passthrough
91
+ displayProperty: { type: String },
92
+ idProperty: { type: String },
93
+ queryStringVariable: { type: String },
94
+ placeholder: { type: String },
95
+ selectDefaultPlaceholder: { type: String },
96
+ resultDataProperty: { type: String },
97
+ maxResults: { type: Number },
98
+ pageParamName: { type: String },
99
+ getSelectResultList: { type: Object },
100
+
101
+ // Related list customisation
102
+ itemTemplate: { type: Object },
103
+ getListItems: { type: Object },
104
+ addRelationLabel: { type: String },
105
+ bodyIdProperty: { type: String },
106
+
107
+ // Internal state
108
+ _items: { type: Array, state: true },
109
+ _selectedId: { type: String, state: true },
110
+ _removingItemId: { type: String, state: true },
111
+ _loadingList: { type: Boolean, state: true },
112
+ };
113
+ }
114
+
115
+ constructor() {
116
+ super();
117
+ this.relatedItems = [];
118
+ this._items = [];
119
+ this.loadFromEndpoint = false;
120
+ this.displayProperty = 'name';
121
+ this.idProperty = 'id';
122
+ this.getListItems = (response) => response.data;
123
+ this.itemTemplate = (item) => html`${item[this.displayProperty]}`;
124
+ this.bodyIdProperty = null;
125
+ this._loadingList = false;
126
+ }
127
+
128
+ updated(changedProperties) {
129
+ if (changedProperties.has('relatedItems') && !this.loadFromEndpoint) {
130
+ this._items = [...this.relatedItems];
131
+ }
132
+ }
133
+
134
+ firstUpdated() {
135
+ this._ajaxlist = this.shadowRoot.getElementById('ajaxlist');
136
+ this._ajaxadd = this.shadowRoot.getElementById('ajaxadd');
137
+ this._ajaxrem = this.shadowRoot.getElementById('ajaxrem');
138
+
139
+ if (this.loadFromEndpoint) {
140
+ this._refreshList();
141
+ }
142
+ }
143
+
144
+ render() {
145
+ return html`
146
+ <dile-ajax
147
+ id="ajaxlist"
148
+ method="get"
149
+ url="${this.endpointList}"
150
+ @ajax-success="${this._onListSuccess}"
151
+ @ajax-error="${this._onListError}"
152
+ ></dile-ajax>
153
+ <dile-ajax
154
+ id="ajaxadd"
155
+ method="post"
156
+ url="${this.endpointAdd}"
157
+ @ajax-success="${this._onAddSuccess}"
158
+ @ajax-error="${this._onAddError}"
159
+ ></dile-ajax>
160
+ <dile-ajax
161
+ id="ajaxrem"
162
+ method="delete"
163
+ @ajax-success="${this._onRemoveSuccess}"
164
+ @ajax-error="${this._onRemoveError}"
165
+ ></dile-ajax>
166
+
167
+ <div class="add-row">
168
+ <dile-ajax-select-crud
169
+ id="theselect"
170
+ endpoint="${this.endpointGet}"
171
+ displayProperty="${this.displayProperty}"
172
+ idProperty="${this.idProperty}"
173
+ queryStringVariable="${this.queryStringVariable}"
174
+ placeholder="${this.placeholder}"
175
+ selectDefaultPlaceholder="${this.selectDefaultPlaceholderComputed(this.selectDefaultPlaceholder, this.translations)}"
176
+ resultDataProperty="${this.resultDataProperty}"
177
+ maxResults="${this.maxResults}"
178
+ pageParamName="${this.pageParamName}"
179
+ .getSelectResultList="${this.getSelectResultList}"
180
+ language="${this.language}"
181
+ @element-changed="${this._onSelectChanged}"
182
+ ></dile-ajax-select-crud>
183
+ <button
184
+ class="add-btn"
185
+ ?disabled="${!this._selectedId}"
186
+ title="${this.addRelationLabelComputed(this.addRelationLabel, this.translations)}"
187
+ @click="${this._addSelectedItem}"
188
+ ><dile-icon .icon="${addIcon}"></dile-icon></button>
189
+ </div>
190
+
191
+ ${this._loadingList
192
+ ? html`<dile-spinner active></dile-spinner>`
193
+ : this._items.length === 0
194
+ ? html`<p class="empty-msg">${this.translations?.empty_list ?? 'There are no items yet'}</p>`
195
+ : this._items.map(item => html`
196
+ <div class="relation-item">
197
+ <span class="relation-item-content">${this.itemTemplate(item)}</span>
198
+ <button
199
+ class="remove-btn"
200
+ ?disabled="${this._removingItemId === item[this.idProperty]}"
201
+ @click="${() => this._removeItem(item)}"
202
+ title="${this.translations?.delete_label ?? 'Delete'}"
203
+ ><dile-icon .icon="${clearIcon}"></dile-icon></button>
204
+ </div>
205
+ `)
206
+ }
207
+ `;
208
+ }
209
+
210
+ _onSelectChanged(e) {
211
+ this._selectedId = e.detail.value || null;
212
+ }
213
+
214
+ _addSelectedItem() {
215
+ if (!this._selectedId) return;
216
+ const bodyKey = this.bodyIdProperty || this.idProperty;
217
+ this._ajaxadd.data = { [bodyKey]: this._selectedId };
218
+ this._ajaxadd.generateRequest();
219
+ }
220
+
221
+ _onAddSuccess() {
222
+ this._selectedId = null;
223
+ this._clearSelect();
224
+ this._refreshList();
225
+ this._dispatch('many-relation-add-success');
226
+ }
227
+
228
+ _onAddError() {
229
+ this._dispatch('many-relation-add-error');
230
+ }
231
+
232
+ _removeItem(item) {
233
+ const id = item[this.idProperty];
234
+ this._removingItemId = id;
235
+ this._ajaxrem.url = `${this.endpointRemove}/${id}`;
236
+ this._ajaxrem.generateRequest();
237
+ }
238
+
239
+ _onRemoveSuccess() {
240
+ this._removingItemId = undefined;
241
+ this._refreshList();
242
+ this._dispatch('many-relation-remove-success');
243
+ }
244
+
245
+ _onRemoveError() {
246
+ this._removingItemId = undefined;
247
+ this._dispatch('many-relation-remove-error');
248
+ }
249
+
250
+ _refreshList() {
251
+ this._loadingList = true;
252
+ this.updateComplete.then(() => this._ajaxlist.generateRequest());
253
+ }
254
+
255
+ _onListSuccess(e) {
256
+ this._items = this.getListItems(e.detail);
257
+ this._loadingList = false;
258
+ }
259
+
260
+ _onListError() {
261
+ this._loadingList = false;
262
+ this._dispatch('many-relation-list-error');
263
+ }
264
+
265
+ _clearSelect() {
266
+ const select = this.shadowRoot.getElementById('theselect');
267
+ if (select) {
268
+ select.clear();
269
+ }
270
+ }
271
+
272
+ _dispatch(eventName) {
273
+ this.dispatchEvent(new CustomEvent(eventName, { bubbles: true, composed: true }));
274
+ }
275
+
276
+ addRelationLabelComputed(label, translations) {
277
+ return label ? label : translations?.add_relation_label ?? 'Add';
278
+ }
279
+
280
+ selectDefaultPlaceholderComputed(value, translations) {
281
+ return value ? value : translations?.select_relation_placeholder ?? 'Select...';
282
+ }
283
+ }
@@ -39,12 +39,15 @@ export const defaultConfig = {
39
39
  updateOperation: {
40
40
  type: 'modal'
41
41
  },
42
- insertOperation: {
43
- type: 'modal'
44
- },
45
42
  computeItemId(element) {
46
43
  return element.id;
47
44
  },
45
+ isItemEditable(element) {
46
+ return true;
47
+ },
48
+ isItemDeletable(element) {
49
+ return true;
50
+ },
48
51
  actions: {
49
52
  list: [
50
53
  {
package/lib/i18n/en.js CHANGED
@@ -54,4 +54,6 @@ export const translations = {
54
54
  extension_allowed: "Only this file extensions are allowed: ",
55
55
  confirm_submit_title: "Confirm submission",
56
56
  confirm_submit_text: "Are you sure you want to submit this form?",
57
+ add_relation_label: "Add",
58
+ select_relation_placeholder: "Select...",
57
59
  };
package/lib/i18n/es.js CHANGED
@@ -54,4 +54,6 @@ export const translations = {
54
54
  extension_allowed: "Solo se permiten estas extensiones: ",
55
55
  confirm_submit_title: "Confirmar envío",
56
56
  confirm_submit_text: "¿Estás seguro de que deseas enviar este formulario?",
57
+ add_relation_label: "Añadir",
58
+ select_relation_placeholder: "Selecciona...",
57
59
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dile/crud",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Components to create a generic crud system based on Web Components and Lit",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -24,12 +24,12 @@
24
24
  },
25
25
  "homepage": "https://dile-components.com/",
26
26
  "dependencies": {
27
- "@dile/ui": "^2.21.2",
27
+ "@dile/ui": "^2.21.3",
28
28
  "axios": "^1.16.0",
29
29
  "lit": "^2.7.0 || ^3.0.0"
30
30
  },
31
31
  "publishConfig": {
32
32
  "access": "public"
33
33
  },
34
- "gitHead": "cde235f0d633aac9a2da770248c7301cef99041a"
34
+ "gitHead": "67e2e845295d11fcdbf3dedcc989d26b821005d4"
35
35
  }