selective-ui 1.0.3 → 1.0.4

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.
@@ -1,4 +1,4 @@
1
- /*! Selective UI v1.0.1 | MIT License */
1
+ /*! Selective UI v1.0.4 | MIT License */
2
2
  /**
3
3
  * @class
4
4
  */
@@ -4184,6 +4184,88 @@ class SearchController {
4184
4184
  return !(!this.#ajaxConfig);
4185
4185
  }
4186
4186
 
4187
+ /**
4188
+ * Load specific options by their values from server
4189
+ * @param {string|string[]} values - Values to load
4190
+ * @returns {Promise<{success: boolean, items: Array, message?: string}>}
4191
+ */
4192
+ async loadByValues(values) {
4193
+ if (!this.#ajaxConfig) {
4194
+ return { success: false, items: [], message: "Ajax not configured" };
4195
+ }
4196
+
4197
+ const valuesArray = Array.isArray(values) ? values : [values];
4198
+ if (valuesArray.length === 0) {
4199
+ return { success: true, items: [] };
4200
+ }
4201
+
4202
+ try {
4203
+ const cfg = this.#ajaxConfig;
4204
+
4205
+ let payload;
4206
+ if (typeof cfg.dataByValues === "function") {
4207
+ payload = cfg.dataByValues(valuesArray);
4208
+ } else {
4209
+ payload = {
4210
+ values: valuesArray.join(","),
4211
+ load_by_values: "1",
4212
+ ...(typeof cfg.data === "function" ? cfg.data("", 0) : (cfg.data || {}))
4213
+ };
4214
+ }
4215
+
4216
+ let response;
4217
+ if (cfg.method === "POST") {
4218
+ const formData = new URLSearchParams();
4219
+ Object.keys(payload).forEach(key => {
4220
+ formData.append(key, payload[key]);
4221
+ });
4222
+
4223
+ response = await fetch(cfg.url, {
4224
+ method: "POST",
4225
+ body: formData,
4226
+ headers: { "Content-Type": "application/x-www-form-urlencoded" }
4227
+ });
4228
+ } else {
4229
+ const params = new URLSearchParams(payload).toString();
4230
+ response = await fetch(`${cfg.url}?${params}`);
4231
+ }
4232
+
4233
+ if (!response.ok) {
4234
+ throw new Error(`HTTP error! status: ${response.status}`);
4235
+ }
4236
+
4237
+ const data = await response.json();
4238
+ const result = this.#parseResponse(data);
4239
+
4240
+ return {
4241
+ success: true,
4242
+ items: result.items
4243
+ };
4244
+ } catch (error) {
4245
+ console.error("Load by values error:", error);
4246
+ return {
4247
+ success: false,
4248
+ message: error.message,
4249
+ items: []
4250
+ };
4251
+ }
4252
+ }
4253
+
4254
+ /**
4255
+ * Check if values exist in current options
4256
+ * @param {string[]} values - Values to check
4257
+ * @returns {{existing: string[], missing: string[]}}
4258
+ */
4259
+ checkMissingValues(values) {
4260
+ const allOptions = Array.from(this.#select.options);
4261
+ const existingValues = allOptions.map(opt => opt.value);
4262
+
4263
+ const existing = values.filter(v => existingValues.includes(v));
4264
+ const missing = values.filter(v => !existingValues.includes(v));
4265
+
4266
+ return { existing, missing };
4267
+ }
4268
+
4187
4269
  /**
4188
4270
  * Configures AJAX settings used for remote searching and pagination.
4189
4271
  *
@@ -4916,13 +4998,7 @@ class SelectBox {
4916
4998
  tag: {
4917
4999
  node: "div",
4918
5000
  classList: "selective-ui-view",
4919
- tabIndex: 0,
4920
- role: "combobox",
4921
- ariaExpanded: "false",
4922
- ariaLabelledby: options.SEID_HOLDER,
4923
- ariaControls: options.SEID_LIST,
4924
- ariaHaspopup: "true",
4925
- ariaMultiselectable: options.multiple ? "true" : "false",
5001
+ tabIndex: 0,
4926
5002
  onkeydown: (e) => {
4927
5003
  if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
4928
5004
  e.preventDefault();
@@ -5242,10 +5318,21 @@ class SelectBox {
5242
5318
  },
5243
5319
  setValue(evtToken = null, value, trigger = true, force = false) {
5244
5320
  !Array.isArray(value) && (value = [value]);
5321
+
5322
+ value = value.filter(v => v !== "" && v != null);
5323
+
5324
+ if (value.length === 0) {
5325
+ superThis.getModelOption().forEach(modelOption => {
5326
+ modelOption["selectedNonTrigger"] = false;
5327
+ });
5328
+ this.change(false, trigger);
5329
+ return;
5330
+ }
5245
5331
 
5246
5332
  if (bindedOptions.multiple && bindedOptions.maxSelected > 0) {
5247
5333
  if (value.length > bindedOptions.maxSelected) {
5248
- return
5334
+ console.warn(`Cannot select more than ${bindedOptions.maxSelected} items`);
5335
+ return;
5249
5336
  }
5250
5337
  }
5251
5338
 
@@ -5253,12 +5340,58 @@ class SelectBox {
5253
5340
  return;
5254
5341
  }
5255
5342
 
5343
+ if (container.searchController?.isAjax()) {
5344
+ const { existing, missing } = container.searchController.checkMissingValues(value);
5345
+
5346
+ if (missing.length > 0) {
5347
+ console.log(`Loading ${missing.length} missing values from server...`);
5348
+
5349
+ (async () => {
5350
+ if (bindedOptions.loadingfield) {
5351
+ container.popup?.showLoading();
5352
+ }
5353
+
5354
+ try {
5355
+ const result = await container.searchController.loadByValues(missing);
5356
+
5357
+ if (result.success && result.items.length > 0) {
5358
+ result.items.forEach(item => {
5359
+ if (missing.includes(item.value)) {
5360
+ item.selected = true;
5361
+ }
5362
+ });
5363
+
5364
+ container.searchController['#applyAjaxResult'](
5365
+ result.items,
5366
+ true,
5367
+ true
5368
+ );
5369
+
5370
+ setTimeout(() => {
5371
+ superThis.getModelOption().forEach(modelOption => {
5372
+ modelOption["selectedNonTrigger"] = value.some(v => v == modelOption["value"]);
5373
+ });
5374
+ this.change(false, false);
5375
+ }, 100);
5376
+ } else if (missing.length > 0) {
5377
+ console.warn(`Could not load ${missing.length} values:`, missing);
5378
+ }
5379
+ } catch (error) {
5380
+ console.error("Error loading missing values:", error);
5381
+ } finally {
5382
+ if (bindedOptions.loadingfield) {
5383
+ container.popup?.hideLoading();
5384
+ }
5385
+ }
5386
+ })();
5387
+ }
5388
+ }
5389
+
5256
5390
  if (trigger) {
5257
5391
  const beforeChangeToken = iEvents.callEvent([this], ...bindedOptions.on.beforeChange);
5258
5392
  if (beforeChangeToken.isCancel) {
5259
5393
  return;
5260
5394
  }
5261
-
5262
5395
  superThis.oldValue = this.value;
5263
5396
  }
5264
5397
 
@@ -5266,7 +5399,7 @@ class SelectBox {
5266
5399
  modelOption["selectedNonTrigger"] = value.some(v => v == modelOption["value"]);
5267
5400
  });
5268
5401
 
5269
- if (!bindedOptions.multiple){
5402
+ if (!bindedOptions.multiple && value.length > 0) {
5270
5403
  container.targetElement.value = value[0];
5271
5404
  }
5272
5405
 
@@ -5318,8 +5451,14 @@ class SelectBox {
5318
5451
 
5319
5452
  container.popup.open();
5320
5453
  container.searchbox.show();
5321
-
5322
- container.tags.ViewPanel.setAttribute("aria-expanded", "true");
5454
+ const ViewPanel = /** @type {HTMLElement} */ (container.tags.ViewPanel);
5455
+ ViewPanel.setAttribute("aria-expanded", "true");
5456
+ ViewPanel.setAttribute("aria-controls", bindedOptions.SEID_LIST);
5457
+ ViewPanel.setAttribute("aria-haspopup", "listbox");
5458
+ ViewPanel.setAttribute("aria-labelledby", bindedOptions.SEID_HOLDER);
5459
+ if (bindedOptions.multiple) {
5460
+ ViewPanel.setAttribute("aria-multiselectable", "true");
5461
+ }
5323
5462
 
5324
5463
  iEvents.callEvent([this], ...bindedOptions.on.show);
5325
5464
 
@@ -5987,7 +6126,7 @@ function markLoaded(name, version, api) {
5987
6126
  console.log(`[${name}] v${version} loaded successfully`);
5988
6127
  }
5989
6128
 
5990
- const version = "1.0.3";
6129
+ const version = "1.0.4";
5991
6130
  const name = "SelectiveUI";
5992
6131
 
5993
6132
  const alreadyLoaded = checkDuplicate(name);