@kamranbaylarov/one-select 1.1.0 → 1.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/README.md CHANGED
@@ -1,891 +1,979 @@
1
- # 🎯 OneSelect - jQuery Multi-Select Dropdown Component
2
-
3
- **Version:** 1.2.0 | **Author:** Kamran Baylarov
4
-
5
- A powerful, flexible, and feature-rich multi-select dropdown component for jQuery.
6
-
7
- ## 📋 Table of Contents
8
-
9
- 1. [Overview](#overview)
10
- 2. [Features](#features)
11
- 3. [Installation](#installation)
12
- 4. [All Parameters](#all-parameters)
13
- 5. [Data Attributes](#data-attributes)
14
- 6. [Methods](#methods)
15
- 7. [Callbacks](#callbacks)
16
- 8. [Examples](#examples)
17
- 9. [AJAX Integration](#ajax-integration)
18
- 10. [Form Submission](#form-submission)
19
- 11. [Badge System](#badge-system)
20
- 12. [CSS Styling](#css-styling)
21
-
22
- ---
23
-
24
- ## 🎯 Overview
25
-
26
- OneSelect is a powerful **jQuery-based** plugin that provides multi-select functionality with comprehensive customization options.
27
-
28
- ### 🚀 Key Features
29
-
30
- - ✅ **Multiple Selection** - Select multiple items with checkboxes
31
- - 🎯 **Select All** - Select all items with one click
32
- - 🏷️️ **Badge System** - Display selected items as badges
33
- - 📤 **External Badges** - Display badges in external elements
34
- - 🔄 **AJAX Support** - Load data dynamically
35
- - 🔍 **Search Feature** - Local filtering or AJAX search with debounce
36
- - 📝 **Form Submission** - Submit data via hidden inputs
37
- - 🎨 **Fully Customizable** - Complete control with 27+ parameters
38
- - 📱 **Responsive** - Works on all devices
39
- - 🌐 **Data Attributes** - Configure via HTML attributes
40
- - 🎪 **Multiple Instances** - Independent selects on same page
41
- - 🌪 **Click Outside** - Close dropdown when clicking outside (default: true)
42
- - 📍 **Smart Positioning** - Dropdown positioned with `position: fixed` using viewport coordinates
43
- - 🔀 **Horizontal Scroll Detection** - Automatically closes on any horizontal scroll to prevent misalignment
44
-
45
- ---
46
-
47
- ## 💡 Features
48
-
49
- ### 📊 Technical Stack
50
-
51
- | Component | Technology |
52
- |-----------|------------|
53
- | **Library** | jQuery (required dependency) |
54
- | **Plugin Type** | jQuery Plugin |
55
- | **Files** | `one-select.js`, `one-select.min.css` |
56
-
57
- ### 🎯 Functionality
58
-
59
- ```
60
- 📦 Data Structures:
61
- ├── String Array: ['Apple', 'Banana', 'Cherry']
62
- └── Object Array: [{id: 1, name: 'Apple'}, ...]
63
-
64
- 🎮 Interactions:
65
- ├── Click to select (checkbox)
66
- ├── "Select All" option
67
- ├── OK button (confirm)
68
- ├── Cancel button (clears selection and closes)
69
- └── × button (remove from badges)
70
-
71
- 📤 Data Flow:
72
- ├── onChange(values, labels) - When selection changes
73
- ├── onOk(values, labels) - When OK clicked
74
- ├── onCancel() - When Cancel clicked
75
- └── AJAX callbacks (beforeLoad, afterLoad, onLoadError)
76
- ```
77
-
78
- ---
79
-
80
- ## 📦 Installation
81
-
82
- ### Via NPM (Recommended)
83
-
84
- ```bash
85
- npm install @kamranbaylarov/one-select
86
- ```
87
-
88
- ### Manual Download
89
-
90
- Download from [GitHub Releases](https://github.com/KamranBeylarov/one-select/releases)
91
-
92
- ### Dependencies
93
-
94
- ```html
95
- <!-- jQuery must be included first -->
96
- <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
97
- ```
98
-
99
- ### Include Files
100
-
101
- **NPM:**
102
- ```html
103
- <!-- CSS -->
104
- <link rel="stylesheet" href="/node_modules/@kamranbaylarov/one-select/css/one-select.min.css">
105
-
106
- <!-- JavaScript -->
107
- <script src="/node_modules/@kamranbaylarov/one-select/js/one-select.min.js"></script>
108
- ```
109
-
110
- **Manual:**
111
- ```html
112
- <!-- CSS -->
113
- <link rel="stylesheet" href="/path/to/one-select.min.css">
114
-
115
- <!-- JavaScript -->
116
- <script src="/path/to/one-select.js"></script>
117
- ```
118
-
119
- ### Create HTML Element
120
-
121
- ```html
122
- <div id="mySelect"></div>
123
- ```
124
-
125
- ### Initialize
126
-
127
- ```javascript
128
- $('#mySelect').oneSelect({
129
- data: ['Apple', 'Banana', 'Cherry']
130
- });
131
- ```
132
-
133
- ---
134
-
135
- ## ⚙️ All Parameters
136
-
137
- | Parameter | Type | Default | Description |
138
- |---------|-----|---------|-------------|
139
- | `placeholder` | String | `'Select options...'` | Placeholder text when nothing selected |
140
- | `selectAllText` | String | `'Select All'` | "Select All" button text |
141
- | `okText` | String | `'OK'` | OK button text |
142
- | `cancelText` | String | `'Cancel'` | Cancel button text |
143
- | `data` | Array | `[]` | Options list (string array only) |
144
- | `value` | Number/Array/null | `null` | Single index or array of indices to pre-select |
145
- | `showCheckbox` | Boolean | `true` | Show/hide checkboxes |
146
- | `showBadges` | Boolean | `false` | Show badges in trigger |
147
- | `showBadgesExternal` | String/null | `null` | External element ID (for badges) |
148
- | `showSearch` | Boolean | `false` | Show search input in dropdown |
149
- | `searchPlaceholder` | String | `'Search...'` | Search input placeholder text |
150
- | `searchUrl` | String/null | `null` | URL for AJAX search (GET request with `q` parameter) |
151
- | `searchDebounceDelay` | Number | `300` | Delay in milliseconds for search debounce |
152
- | `closeOnScroll` | Boolean | `false` | Close dropdown on page scroll |
153
- | `closeOnOutside` | Boolean | `true` | Close dropdown when clicking outside |
154
- | `submitForm` | Boolean | `false` | Submit form on OK click |
155
- | `submitOnOutside` | Boolean | `false` | Submit form on outside click |
156
- | `formId` | String/null | `null` | Specific form ID (null: parent form) |
157
- | `name` | String/null | `null` | Hidden input name attribute |
158
- | `multiple` | Boolean | `true` | Submit as array (name[]) |
159
- | `ajax` | Object/null | `null` | AJAX configuration object |
160
- | `autoLoad` | Boolean | `true` | Auto load data via AJAX |
161
- | `beforeLoad` | Function/null | `null` | Called before AJAX |
162
- | `afterLoad` | Function/null | `null` | Called after AJAX success |
163
- | `onLoadError` | Function/null | `null` | Called on AJAX error |
164
- | `onChange` | Function/null | `null` | Called when selection changes |
165
- | `onSelect` | Function/null | `null` | Previous version of onChange |
166
- | `onOk` | Function/null | `null` | Called when OK clicked |
167
- | `onCancel` | Function/null | `null` | Called when Cancel clicked |
168
-
169
- ---
170
-
171
- ## 🏷️ Data Attributes
172
-
173
- All parameters can be set via HTML data attributes. Data attributes **override JS parameters**.
174
-
175
- | Data Attribute | Parameter | Type | Example |
176
- |----------------|----------|-----|---------|
177
- | `data-ones-placeholder` | `placeholder` | String | `data-ones-placeholder="Select..."` |
178
- | `data-ones-select-all-text` | `selectAllText` | String | `data-ones-select-all-text="Select All"` |
179
- | `data-ones-ok-text` | `okText` | String | `data-ones-ok-text="Confirm"` |
180
- | `data-ones-cancel-text` | `cancelText` | String | `data-ones-cancel-text="Cancel"` |
181
- | `data-ones-data` | `data` | Array | `data-ones-data='["A","B","C"]'` |
182
- | `data-ones-value` | `value` | Number/Array | `data-ones-value='"0"'` or `data-ones-value='["0","2"]'` |
183
- | `data-ones-name` | `name` | String | `data-ones-name="items"` |
184
- | `data-ones-multiple` | `multiple` | Boolean | `data-ones-multiple="true"` |
185
- | `data-ones-show-checkbox` | `showCheckbox` | Boolean | `data-ones-show-checkbox="false"` |
186
- | `data-ones-show-badges` | `showBadges` | Boolean | `data-ones-show-badges="true"` |
187
- | `data-ones-show-badges-external` | `showBadgesExternal` | String | `data-ones-show-badges-external="badgesDiv"` |
188
- | `data-ones-show-search` | `showSearch` | Boolean | `data-ones-show-search="true"` |
189
- | `data-ones-search-placeholder` | `searchPlaceholder` | String | `data-ones-search-placeholder="Search items..."` |
190
- | `data-ones-search-url` | `searchUrl` | String | `data-ones-search-url="/api/search"` |
191
- | `data-ones-search-debounce-delay` | `searchDebounceDelay` | Number | `data-ones-search-debounce-delay="500"` |
192
- | `data-ones-close-on-scroll` | `closeOnScroll` | Boolean | `data-ones-close-on-scroll="true"` |
193
- | `data-ones-close-on-outside` | `closeOnOutside` | Boolean | `data-ones-close-on-outside="true"` |
194
- | `data-ones-submit-form` | `submitForm` | Boolean | `data-ones-submit-form="true"` |
195
- | `data-ones-submit-on-outside` | `submitOnOutside` | Boolean | `data-ones-submit-on-outside="true"` |
196
- | `data-ones-form-id` | `formId` | String | `data-ones-form-id="myForm"` |
197
- | `data-ones-auto-load` | `autoLoad` | Boolean | `data-ones-auto-load="false"` |
198
- | `data-ones-ajax` | `ajax` | Object | `data-ones-ajax='{"url": "/api/items"}'` |
199
-
200
- ### Example:
201
-
202
- ```html
203
- <div id="mySelect"
204
- data-ones-placeholder="Select products..."
205
- data-ones-data='["Apple", "Banana", "Cherry"]'
206
- data-ones-show-badges="true"
207
- data-ones-name="fruits"
208
- ></div>
209
-
210
- <script>
211
- $('#mySelect').oneSelect({
212
- // JS options (data attributes override these)
213
- });
214
- </script>
215
- ```
216
-
217
- ---
218
-
219
- ## 🔧 Methods
220
-
221
- Call via jQuery plugin method:
222
-
223
- ```javascript
224
- // Get selected values
225
- var values = $('#mySelect').oneSelect('getValues');
226
- var labels = $('#mySelect').oneSelect('getLabels');
227
-
228
- // Set selection
229
- $('#mySelect').oneSelect('value', ['Apple', 'Banana']);
230
-
231
- // Update data
232
- $('#mySelect').oneSelect('updateData', ['New', 'Data']);
233
-
234
- // Load data via AJAX
235
- $('#mySelect').oneSelect('loadData');
236
-
237
- // Selection control
238
- $('#mySelect').oneSelect('selectAll');
239
- $('#mySelect').oneSelect('unselectAll');
240
- $('#mySelect').oneSelect('select', 'Apple');
241
- $('#mySelect').oneSelect('unselect', 'Banana');
242
- $('#mySelect').oneSelect('toggleSelection', 'Cherry');
243
-
244
- // Dropdown control
245
- $('#mySelect').oneSelect('open');
246
- $('#mySelect').oneSelect('close');
247
-
248
- // Get instance ID
249
- var instanceId = $('#mySelect').oneSelect('getInstanceId');
250
-
251
- // Get instance object
252
- var instance = OneSelect.getInstance(instanceId);
253
-
254
- // Get all instances
255
- var allInstances = OneSelect.getAllInstances();
256
-
257
- // Destroy
258
- $('#mySelect').oneSelect('destroy');
259
- ```
260
-
261
- ---
262
-
263
- ## 🎯 Callbacks
264
-
265
- ### onChange(values, labels)
266
-
267
- Called every time selection changes. Most important callback.
268
-
269
- ```javascript
270
- $('#mySelect').oneSelect({
271
- data: ['A', 'B', 'C'],
272
- onChange: function(values, labels) {
273
- console.log('Values:', values);
274
- console.log('Labels:', labels);
275
- // values: ['A', 'C']
276
- // labels: ['A', 'C']
277
- }
278
- });
279
- ```
280
-
281
- ### onOk(values, labels)
282
-
283
- Called when OK button is clicked.
284
-
285
- ```javascript
286
- $('#mySelect').oneSelect({
287
- onOk: function(values, labels) {
288
- alert('Selected: ' + labels.join(', '));
289
- }
290
- });
291
- ```
292
-
293
- ### onCancel()
294
-
295
- Called when Cancel button is clicked and clears all selections.
296
-
297
- ```javascript
298
- $('#mySelect').oneSelect({
299
- onCancel: function() {
300
- console.log('Selection cancelled');
301
- }
302
- });
303
- ```
304
-
305
- ### AJAX Callbacks
306
-
307
- ```javascript
308
- $('#mySelect').oneSelect({
309
- ajax: {
310
- url: '/api/items',
311
- method: 'GET'
312
- },
313
- beforeLoad: function() {
314
- $('#loading').show();
315
- },
316
- afterLoad: function(response) {
317
- $('#loading').hide();
318
- console.log('Data loaded:', response);
319
- },
320
- onLoadError: function(error) {
321
- $('#error').text('Error: ' + error);
322
- }
323
- });
324
- ```
325
-
326
- ---
327
-
328
- ## 📚 Examples
329
-
330
- ### 1. Basic Usage
331
-
332
- ```javascript
333
- $('#mySelect').oneSelect({
334
- placeholder: 'Select fruits...',
335
- data: ['Apple', 'Banana', 'Cherry', 'Mango']
336
- });
337
- ```
338
-
339
- ### 2. How It Works
340
-
341
- **Data Format:** String array only
342
-
343
- ```javascript
344
- data: ['Apple', 'Banana', 'Cherry']
345
- ```
346
-
347
- **Result:**
348
- - **value** (what gets submitted): `0, 1, 2` (array indices)
349
- - **label** (what user sees): `'Apple', 'Banana', 'Cherry'`
350
-
351
- **Perfect for:**
352
- - PHP associative arrays converted to indexed arrays
353
- - Backend IDs as array indices
354
- - Display values as array elements
355
-
356
- **Example with PHP data:**
357
- ```php
358
- // PHP
359
- $items = [
360
- 5 => "M.Hadi_9_6/1",
361
- 6 => "Sarayevo_20_4/2/1",
362
- 7 => "Sarayevo_13B_3/2/1"
363
- ];
364
- echo json_encode(array_values($items));
365
- // Output: ["M.Hadi_9_6/1","Sarayevo_20_4/2/1","Sarayevo_13B_3/2/1"]
366
- ```
367
-
368
- ```javascript
369
- // JavaScript
370
- $('#mySelect').oneSelect({
371
- data: ["M.Hadi_9_6/1", "Sarayevo_20_4/2/1", "Sarayevo_13B_3/2/1"]
372
- });
373
- // value: 0, 1, 2 (array indices)
374
- // label: "M.Hadi_9_6/1", "Sarayevo_20_4/2/1", "Sarayevo_13B_3/2/1"
375
- ```
376
-
377
- ### 3. Value Parameter (Pre-selected Items)
378
-
379
- ```javascript
380
- // Single value (by index)
381
- $('#mySelect').oneSelect({
382
- data: ['Apple', 'Banana', 'Cherry', 'Mango'],
383
- value: 0 // Index 0 ('Apple') will be selected
384
- });
385
-
386
- // Array with multiple selection
387
- $('#mySelect').oneSelect({
388
- data: ['Apple', 'Banana', 'Cherry', 'Mango'],
389
- value: [0, 2], // Indices 0 ('Apple') and 2 ('Cherry') will be selected
390
- showBadges: true
391
- });
392
-
393
- // HTML data attribute
394
- <div class="one-select"
395
- data-ones-data='["Apple", "Banana", "Cherry"]'
396
- data-ones-value='"0"'> <!-- Single index -->
397
- </div>
398
-
399
- <!-- OR -->
400
- <div class="one-select"
401
- data-ones-data='["Apple", "Banana", "Cherry"]'
402
- data-ones-value='["0", "2"]'> <!-- Multiple indices -->
403
- </div>
404
- ```
405
-
406
- ### 4. Badge System
407
-
408
- ```javascript
409
- // Trigger badges
410
- $('#mySelect').oneSelect({
411
- data: ['A', 'B', 'C'],
412
- showBadges: true
413
- });
414
-
415
- // External badges
416
- <div id="badgesContainer"></div>
417
-
418
- $('#mySelect').oneSelect({
419
- data: ['A', 'B', 'C'],
420
- showBadgesExternal: 'badgesContainer'
421
- });
422
- ```
423
-
424
- ### 5. Form Submission
425
-
426
- ```javascript
427
- $('#mySelect').oneSelect({
428
- data: ['Item 1', 'Item 2'],
429
- name: 'items',
430
- multiple: true,
431
- submitForm: true,
432
- formId: 'myForm'
433
- });
434
- ```
435
-
436
- Backend example:
437
- ```javascript
438
- // Receive items array: [0, 1] (indices of selected items)
439
- // Use indices to get original values from your data array
440
- ```
441
-
442
- ### 6. AJAX Data Loading
443
-
444
- ```javascript
445
- $('#mySelect').oneSelect({
446
- ajax: {
447
- url: '/api/categories',
448
- method: 'GET'
449
- },
450
- beforeLoad: function() {
451
- console.log('Loading...');
452
- },
453
- afterLoad: function(response) {
454
- console.log('Loaded:', response);
455
- }
456
- });
457
- ```
458
-
459
- ### 7. Multiple Instances
460
-
461
- ```javascript
462
- // Each has independent ID
463
- var fruits = $('#fruits').oneSelect({
464
- data: ['Apple', 'Banana', 'Cherry']
465
- });
466
-
467
- var colors = $('#colors').oneSelect({
468
- data: ['Red', 'Blue', 'Green']
469
- });
470
-
471
- // External control over instances
472
- var fruitsInstance = OneSelect.getInstance(
473
- $('#fruits').oneSelect('getInstanceId')
474
- );
475
- fruitsInstance.selectAll();
476
- ```
477
-
478
- ### 8. Search Feature
479
-
480
- ```javascript
481
- // Enable local search (filters existing data)
482
- $('#mySelect').oneSelect({
483
- data: ['Apple', 'Banana', 'Cherry', 'Mango', 'Orange', 'Grape'],
484
- showSearch: true,
485
- searchPlaceholder: 'Type to search...'
486
- });
487
-
488
- // Enable AJAX search (with debounce)
489
- $('#mySelect').oneSelect({
490
- showSearch: true,
491
- searchUrl: '/api/search',
492
- searchDebounceDelay: 500,
493
- searchPlaceholder: 'Search items...'
494
- });
495
-
496
- // HTML data attribute example (local search)
497
- <div class="one-select"
498
- data-ones-data='["Apple", "Banana", "Cherry", "Mango"]'
499
- data-ones-show-search="true"
500
- data-ones-search-placeholder="Find a fruit...">
501
- </div>
502
-
503
- // HTML data attribute example (AJAX search)
504
- <div class="one-select"
505
- data-ones-show-search="true"
506
- data-ones-search-url="/api/customers/search"
507
- data-ones-search-debounce-delay="300"
508
- data-ones-search-placeholder="Search customers...">
509
- </div>
510
- ```
511
-
512
- **AJAX Search Server Response Format:**
513
-
514
- The server should respond with JSON in one of these formats:
515
-
516
- ```json
517
- // Direct array
518
- ["Apple", "Banana", "Cherry"]
519
-
520
- // Wrapped with 'data'
521
- {
522
- "data": [{"value": 1, "label": "Apple"}, {"value": 2, "label": "Banana"}]
523
- }
524
-
525
- // Wrapped with 'results'
526
- {
527
- "results": ["Apple", "Banana"]
528
- }
529
- ```
530
-
531
- The request will be sent as `GET /api/search?q=searchterm`
532
-
533
- ---
534
-
535
- ## 📦 Horizontal Scroll Behavior
536
-
537
- **Automatic dropdown closing on horizontal scroll:**
538
-
539
- The dropdown automatically closes when the user performs any horizontal scrolling action while the dropdown is open. This prevents the dropdown from appearing in the wrong position when:
540
-
541
- - Tables with `overflow-x: auto` are scrolled horizontally
542
- - Any scrollable container is scrolled horizontally
543
- - Touchpad/trackpad horizontal gestures are used
544
- - Mouse wheel horizontal scrolling is performed
545
-
546
- ### How It Works
547
-
548
- The plugin uses two methods to detect horizontal scroll:
549
-
550
- 1. **Wheel Event Detection**: Detects horizontal mouse/touchpad scrolling in real-time
551
- 2. **Periodic Scroll Checking**: Every 50ms, checks if any scrollable element's `scrollLeft` position has changed
552
-
553
- When horizontal scroll is detected, the dropdown immediately closes.
554
-
555
- ### Example
556
-
557
- ```html
558
- <div style="overflow-x: auto; width: 100%;">
559
- <table style="width: 2000px;">
560
- <tr>
561
- <td>
562
- <div id="mySelect"></div>
563
- </td>
564
- <td>Other columns...</td>
565
- </tr>
566
- </table>
567
- </div>
568
-
569
- <script>
570
- $('#mySelect').oneSelect({
571
- data: ['Apple', 'Banana', 'Cherry']
572
- });
573
- </script>
574
- ```
575
-
576
- **Behavior:**
577
- - User opens dropdown ✅
578
- - User scrolls table horizontally → **Dropdown closes automatically** ✅
579
-
580
- ---
581
-
582
- ## 🔄 AJAX Integration
583
-
584
- ### AJAX Configuration
585
-
586
- ```javascript
587
- $('#mySelect').oneSelect({
588
- ajax: {
589
- url: '/api/items',
590
- method: 'GET',
591
- data: { category: 'fruits', active: true }
592
- },
593
- autoLoad: false
594
- });
595
-
596
- // Manual load
597
- $('#loadBtn').on('click', function() {
598
- $('#mySelect').oneSelect('loadData', {
599
- url: '/api/different-items',
600
- data: { filter: 'active' }
601
- });
602
- });
603
- ```
604
-
605
- ### Supported Response Formats
606
-
607
- **1. Direct array:**
608
- ```json
609
- ["Apple", "Banana", "Cherry"]
610
- ```
611
-
612
- **2. Wrapped with 'data':**
613
- ```json
614
- {
615
- "data": ["Apple", "Banana"]
616
- }
617
- ```
618
-
619
- **3. Wrapped with 'results':**
620
- ```json
621
- {
622
- "results": ["Apple", "Banana"]
623
- }
624
- ```
625
-
626
- **4. Object array:**
627
- ```json
628
- [
629
- {"value": 1, "label": "Apple"},
630
- {"value": 2, "label": "Banana"}
631
- ]
632
- ```
633
-
634
- ---
635
-
636
- ## 📝 Form Submission
637
-
638
- ### Hidden Inputs
639
-
640
- Component automatically creates `<input type="hidden">` elements:
641
-
642
- ```javascript
643
- $('#mySelect').oneSelect({
644
- name: 'items',
645
- multiple: true,
646
- data: ['A', 'B', 'C']
647
- });
648
- ```
649
-
650
- HTML result:
651
- ```html
652
- <input type="hidden" name="items[]" value="A">
653
- <input type="hidden" name="items[]" value="B">
654
- <input type="hidden" name="items[]" value="C">
655
- ```
656
-
657
- ---
658
-
659
- ## 🏷️ Badge System
660
-
661
- ### Trigger Badges
662
-
663
- ```javascript
664
- $('#mySelect').oneSelect({
665
- showBadges: true
666
- });
667
- ```
668
-
669
- Result: `[Apple ×] [Banana ×] [Cherry ×]`
670
-
671
- ### External Badges
672
-
673
- ```javascript
674
- <div id="myBadges"></div>
675
-
676
- $('#mySelect').oneSelect({
677
- showBadgesExternal: 'myBadges'
678
- });
679
- ```
680
-
681
- ### Badge Properties
682
-
683
- - **Background:** `#3b82f6` (light blue)
684
- - **Text color:** `#fff` (white)
685
- - **Remove button (×):** `#fff` (white)
686
- - **Hover:** Light gray background
687
- - **Remove:** Clicking × button unselects item
688
-
689
- ### Both Together
690
-
691
- ```javascript
692
- $('#mySelect').oneSelect({
693
- showBadges: true, // Badge in trigger
694
- showBadgesExternal: 'myBadges' // Badge outside as well
695
- });
696
- ```
697
-
698
- ---
699
-
700
- ## 🎨 CSS Styling
701
-
702
- Main CSS classes with `cms-` prefix:
703
-
704
- ```css
705
- .one-select /* Main container (relative positioned) */
706
- .cms-wrapper /* Wrapper (relative positioned) */
707
- .cms-trigger /* Button that opens dropdown */
708
- .cms-selected-text /* Selected text */
709
- .cms-dropdown /* Dropdown menu (absolute positioned) */
710
- .cms-search-wrapper /* Search input wrapper */
711
- .cms-search-input /* Search input field */
712
- .cms-options-container /* Options container */
713
- .cms-options-container.cms-loading /* Loading state for AJAX search */
714
- .cms-option /* Single option */
715
- .cms-option.selected /* Selected option */
716
- .cms-option.select-all /* "Select All" option */
717
- .cms-badge /* Badge */
718
- .cms-badge-remove /* Badge × button */
719
- .cms-btn /* OK/Cancel buttons */
720
- ```
721
-
722
- ### DOM Structure
723
-
724
- ```html
725
- <!-- Wrapper element (your container) -->
726
- <div class="one-select">
727
- <div class="cms-wrapper">
728
- <div class="cms-trigger">
729
- <span class="cms-selected-text">Select...</span>
730
- </div>
731
- </div>
732
- </div>
733
-
734
- <!-- Dropdown is appended to body with dynamic positioning -->
735
- <div class="cms-dropdown" style="position: fixed; top: ...; left: ...; width: ...;">
736
- <!-- Search input (when showSearch is true) -->
737
- <div class="cms-search-wrapper">
738
- <input type="text" class="cms-search-input" placeholder="Search...">
739
- </div>
740
- <div class="cms-options-container">
741
- <div class="cms-option select-all">...</div>
742
- <div class="cms-option">...</div>
743
- </div>
744
- <div class="cms-buttons">
745
- <button class="cms-btn cms-btn-ok">OK</button>
746
- <button class="cms-btn cms-btn-cancel">Cancel</button>
747
- </div>
748
- </div>
749
- ```
750
-
751
- **Note:** The dropdown is positioned using `position: fixed` with viewport coordinates (`getBoundingClientRect()`). This ensures it stays correctly positioned even when parent elements scroll.
752
-
753
- ### Custom CSS Example
754
-
755
- ```css
756
- /* Selected option styling */
757
- .cms-option.selected label {
758
- font-weight: 600;
759
- color: #007bff;
760
- }
761
-
762
- /* Badge styling */
763
- .cms-badge {
764
- background: #3b82f6;
765
- color: #fff;
766
- border-radius: 12px;
767
- padding: 3px 8px;
768
- }
769
-
770
- /* Search input styling */
771
- .cms-search-input {
772
- background: #f8f9fa;
773
- border-color: #dee2e6;
774
- }
775
- .cms-search-input:focus {
776
- border-color: #3b82f6;
777
- box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
778
- }
779
-
780
- /* Custom dropdown width */
781
- .one-select {
782
- width: 400px;
783
- }
784
- ```
785
-
786
- ---
787
-
788
- ## 🚀 Quick Usage Examples
789
-
790
- ```javascript
791
- // 1. Simple
792
- $('#select1').oneSelect({
793
- data: ['A', 'B', 'C']
794
- });
795
-
796
- // 2. Pre-selected items (by index)
797
- $('#select2').oneSelect({
798
- data: ['Apple', 'Banana', 'Cherry'],
799
- value: [0, 2] // Selects indices 0 and 2
800
- });
801
-
802
- // 3. Badges
803
- $('#select3').oneSelect({
804
- data: ['X', 'Y', 'Z'],
805
- showBadges: true
806
- });
807
-
808
- // 4. Form submission
809
- $('#select4').oneSelect({
810
- data: ['P1', 'P2'],
811
- name: 'products',
812
- multiple: true,
813
- submitForm: true,
814
- formId: 'myForm'
815
- });
816
-
817
- // 5. AJAX
818
- $('#select5').oneSelect({
819
- ajax: {url: '/api/items'},
820
- autoLoad: false
821
- });
822
-
823
- // 6. Click outside behavior
824
- $('#select6').oneSelect({
825
- closeOnOutside: true // Close when clicking outside (default)
826
- });
827
-
828
- // 7. Search feature (local filtering)
829
- $('#select7').oneSelect({
830
- data: ['Apple', 'Banana', 'Cherry', 'Mango', 'Orange'],
831
- showSearch: true,
832
- searchPlaceholder: 'Search fruits...'
833
- });
834
-
835
- // 8. AJAX search with debounce
836
- $('#select8').oneSelect({
837
- showSearch: true,
838
- searchUrl: '/api/search',
839
- searchDebounceDelay: 500
840
- });
841
- ```
842
-
843
- ---
844
-
845
- ## 📞 Support
846
-
847
- ### Links
848
-
849
- - **NPM Package:** [@kamranbaylarov/one-select](https://www.npmjs.com/package/@kamranbaylarov/one-select)
850
- - **GitHub:** [KamranBeylarov/one-select](https://github.com/KamranBeylarov/one-select)
851
- - **Issues:** [Report issues](https://github.com/KamranBeylarov/one-select/issues)
852
-
853
- ### Installation
854
-
855
- ```bash
856
- # NPM
857
- npm install @kamranbaylarov/one-select
858
-
859
- # CDN (coming soon)
860
- ```
861
-
862
- ### Project Structure
863
-
864
- ```
865
- one-select/
866
- ├── css/
867
- │ └── one-select.min.css
868
- ├── js/
869
- │ ├── one-select.js
870
- │ └── one-select.min.js
871
- ├── package.json
872
- └── README.md
873
- ```
874
-
875
- ### License
876
-
877
- MIT License - Feel free to use in your projects!
878
-
879
- ---
880
-
881
- ## 🎯 Browser Support
882
-
883
- - Chrome (latest)
884
- - Firefox (latest)
885
- - Safari (latest)
886
- - Edge (latest)
887
- - Opera (latest)
888
-
889
- ---
890
-
891
- **OneSelect** makes multi-select dropdowns simple and powerful! 🚀
1
+ # 🎯 OneSelect - jQuery Multi-Select Dropdown Component
2
+
3
+ **Version:** 1.2.0 | **Author:** Kamran Baylarov
4
+
5
+ A powerful, flexible, and feature-rich multi-select dropdown component for jQuery.
6
+
7
+ ## 📋 Table of Contents
8
+
9
+ 1. [Overview](#overview)
10
+ 2. [Features](#features)
11
+ 3. [Installation](#installation)
12
+ 4. [All Parameters](#all-parameters)
13
+ 5. [Data Attributes](#data-attributes)
14
+ 6. [Methods](#methods)
15
+ 7. [Callbacks](#callbacks)
16
+ 8. [Examples](#examples)
17
+ 9. [AJAX Integration](#ajax-integration)
18
+ 10. [Form Submission](#form-submission)
19
+ 11. [Badge System](#badge-system)
20
+ 12. [CSS Styling](#css-styling)
21
+
22
+ ---
23
+
24
+ ## 🎯 Overview
25
+
26
+ OneSelect is a powerful **jQuery-based** plugin that provides multi-select functionality with comprehensive customization options.
27
+
28
+ ### 🚀 Key Features
29
+
30
+ - ✅ **Multiple Selection** - Select multiple items with checkboxes
31
+ - 🎯 **Select All** - Select all items with one click
32
+ - 🏷️️ **Badge System** - Display selected items as badges
33
+ - 📤 **External Badges** - Display badges in external elements
34
+ - 🔄 **AJAX Support** - Load data dynamically
35
+ - 🔍 **Search Feature** - Local filtering or AJAX search with debounce
36
+ - 📝 **Form Submission** - Submit data via hidden inputs
37
+ - 🎨 **Fully Customizable** - Complete control with 27+ parameters
38
+ - 📱 **Responsive** - Works on all devices
39
+ - 🌐 **Data Attributes** - Configure via HTML attributes
40
+ - 🔄 **Real-time Sync** - `data-ones-value` attribute updates automatically
41
+ - 🎪 **Multiple Instances** - Independent selects on same page
42
+ - 🌪 **Click Outside** - Close dropdown when clicking outside (default: true)
43
+ - 📍 **Smart Positioning** - Dropdown positioned with `position: fixed` using viewport coordinates
44
+ - 🔀 **Horizontal Scroll Detection** - Automatically closes on any horizontal scroll to prevent misalignment
45
+
46
+ ---
47
+
48
+ ## 💡 Features
49
+
50
+ ### 📊 Technical Stack
51
+
52
+ | Component | Technology |
53
+ |-----------|------------|
54
+ | **Library** | jQuery (required dependency) |
55
+ | **Plugin Type** | jQuery Plugin |
56
+ | **Files** | `one-select.js`, `one-select.min.css` |
57
+
58
+ ### 🎯 Functionality
59
+
60
+ ```
61
+ 📦 Data Structures:
62
+ ├── String Array: ['Apple', 'Banana', 'Cherry']
63
+ └── Object Array: [{id: 1, name: 'Apple'}, ...]
64
+
65
+ 🎮 Interactions:
66
+ ├── Click to select (checkbox)
67
+ ├── "Select All" option
68
+ ├── OK button (confirm)
69
+ ├── Cancel button (clears selection and closes)
70
+ └── × button (remove from badges)
71
+
72
+ 📤 Data Flow:
73
+ ├── onChange(values, labels) - When selection changes
74
+ ├── onOk(values, labels) - When OK clicked
75
+ ├── onCancel() - When Cancel clicked
76
+ ├── data-ones-value sync - Real-time attribute updates
77
+ └── AJAX callbacks (beforeLoad, afterLoad, onLoadError)
78
+ ```
79
+
80
+ ---
81
+
82
+ ## 📦 Installation
83
+
84
+ ### Via NPM (Recommended)
85
+
86
+ ```bash
87
+ npm install @kamranbaylarov/one-select
88
+ ```
89
+
90
+ ### Manual Download
91
+
92
+ Download from [GitHub Releases](https://github.com/KamranBeylarov/one-select/releases)
93
+
94
+ ### Dependencies
95
+
96
+ ```html
97
+ <!-- jQuery must be included first -->
98
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
99
+ ```
100
+
101
+ ### Include Files
102
+
103
+ **NPM:**
104
+ ```html
105
+ <!-- CSS -->
106
+ <link rel="stylesheet" href="/node_modules/@kamranbaylarov/one-select/css/one-select.min.css">
107
+
108
+ <!-- JavaScript -->
109
+ <script src="/node_modules/@kamranbaylarov/one-select/js/one-select.min.js"></script>
110
+ ```
111
+
112
+ **Manual:**
113
+ ```html
114
+ <!-- CSS -->
115
+ <link rel="stylesheet" href="/path/to/one-select.min.css">
116
+
117
+ <!-- JavaScript -->
118
+ <script src="/path/to/one-select.js"></script>
119
+ ```
120
+
121
+ ### Create HTML Element
122
+
123
+ ```html
124
+ <div id="mySelect"></div>
125
+ ```
126
+
127
+ ### Initialize
128
+
129
+ ```javascript
130
+ $('#mySelect').oneSelect({
131
+ data: ['Apple', 'Banana', 'Cherry']
132
+ });
133
+ ```
134
+
135
+ ---
136
+
137
+ ## ⚙️ All Parameters
138
+
139
+ | Parameter | Type | Default | Description |
140
+ |---------|-----|---------|-------------|
141
+ | `placeholder` | String | `'Select options...'` | Placeholder text when nothing selected |
142
+ | `selectAllText` | String | `'Select All'` | "Select All" button text |
143
+ | `okText` | String | `'OK'` | OK button text |
144
+ | `cancelText` | String | `'Cancel'` | Cancel button text |
145
+ | `data` | Array/Object | `[]` | Options list (string array or key-value object) |
146
+ | `value` | Number/Array/String/null | `null` | Single index, key, or array to pre-select |
147
+ | `showCheckbox` | Boolean | `true` | Show/hide checkboxes |
148
+ | `showBadges` | Boolean | `false` | Show badges in trigger |
149
+ | `showBadgesExternal` | String/null | `null` | External element ID (for badges) |
150
+ | `showSearch` | Boolean | `false` | Show search input in dropdown |
151
+ | `searchPlaceholder` | String | `'Search...'` | Search input placeholder text |
152
+ | `searchUrl` | String/null | `null` | URL for AJAX search (GET request with `q` parameter) |
153
+ | `searchDebounceDelay` | Number | `300` | Delay in milliseconds for search debounce |
154
+ | `closeOnScroll` | Boolean | `false` | Close dropdown on page scroll |
155
+ | `closeOnOutside` | Boolean | `true` | Close dropdown when clicking outside |
156
+ | `submitForm` | Boolean | `false` | Submit form on OK click |
157
+ | `submitOnOutside` | Boolean | `false` | Submit form on outside click |
158
+ | `formId` | String/null | `null` | Specific form ID (null: parent form) |
159
+ | `name` | String/null | `null` | Hidden input name attribute |
160
+ | `multiple` | Boolean | `true` | Submit as array (name[]) |
161
+ | `ajax` | Object/null | `null` | AJAX configuration object |
162
+ | `autoLoad` | Boolean | `true` | Auto load data via AJAX |
163
+ | `beforeLoad` | Function/null | `null` | Called before AJAX |
164
+ | `afterLoad` | Function/null | `null` | Called after AJAX success |
165
+ | `onLoadError` | Function/null | `null` | Called on AJAX error |
166
+ | `onChange` | Function/null | `null` | Called when selection changes |
167
+ | `onSelect` | Function/null | `null` | Previous version of onChange |
168
+ | `onOk` | Function/null | `null` | Called when OK clicked |
169
+ | `onCancel` | Function/null | `null` | Called when Cancel clicked |
170
+
171
+ ---
172
+
173
+ ## 🏷️ Data Attributes
174
+
175
+ All parameters can be set via HTML data attributes. Data attributes **override JS parameters**.
176
+
177
+ | Data Attribute | Parameter | Type | Example |
178
+ |----------------|----------|-----|---------|
179
+ | `data-ones-placeholder` | `placeholder` | String | `data-ones-placeholder="Select..."` |
180
+ | `data-ones-select-all-text` | `selectAllText` | String | `data-ones-select-all-text="Select All"` |
181
+ | `data-ones-ok-text` | `okText` | String | `data-ones-ok-text="Confirm"` |
182
+ | `data-ones-cancel-text` | `cancelText` | String | `data-ones-cancel-text="Cancel"` |
183
+ | `data-ones-data` | `data` | Array | `data-ones-data='["A","B","C"]'` |
184
+ | `data-ones-value` | `value` | Number/Array | `data-ones-value='"0"'` or `data-ones-value='["0","2"]'` (Real-time sync, removed when empty) |
185
+ | `data-ones-name` | `name` | String | `data-ones-name="items"` |
186
+ | `data-ones-multiple` | `multiple` | Boolean | `data-ones-multiple="true"` |
187
+ | `data-ones-show-checkbox` | `showCheckbox` | Boolean | `data-ones-show-checkbox="false"` |
188
+ | `data-ones-show-badges` | `showBadges` | Boolean | `data-ones-show-badges="true"` |
189
+ | `data-ones-show-badges-external` | `showBadgesExternal` | String | `data-ones-show-badges-external="badgesDiv"` |
190
+ | `data-ones-show-search` | `showSearch` | Boolean | `data-ones-show-search="true"` |
191
+ | `data-ones-search-placeholder` | `searchPlaceholder` | String | `data-ones-search-placeholder="Search items..."` |
192
+ | `data-ones-search-url` | `searchUrl` | String | `data-ones-search-url="/api/search"` |
193
+ | `data-ones-search-debounce-delay` | `searchDebounceDelay` | Number | `data-ones-search-debounce-delay="500"` |
194
+ | `data-ones-close-on-scroll` | `closeOnScroll` | Boolean | `data-ones-close-on-scroll="true"` |
195
+ | `data-ones-close-on-outside` | `closeOnOutside` | Boolean | `data-ones-close-on-outside="true"` |
196
+ | `data-ones-submit-form` | `submitForm` | Boolean | `data-ones-submit-form="true"` |
197
+ | `data-ones-submit-on-outside` | `submitOnOutside` | Boolean | `data-ones-submit-on-outside="true"` |
198
+ | `data-ones-form-id` | `formId` | String | `data-ones-form-id="myForm"` |
199
+ | `data-ones-auto-load` | `autoLoad` | Boolean | `data-ones-auto-load="false"` |
200
+ | `data-ones-ajax` | `ajax` | String/Object | `data-ones-ajax="/api/items"` or `data-ones-ajax='{"url": "/api/items","method":"POST"}'` |
201
+
202
+ ### Example:
203
+
204
+ ```html
205
+ <div id="mySelect"
206
+ data-ones-placeholder="Select products..."
207
+ data-ones-data='["Apple", "Banana", "Cherry"]'
208
+ data-ones-show-badges="true"
209
+ data-ones-name="fruits"
210
+ ></div>
211
+
212
+ <script>
213
+ $('#mySelect').oneSelect({
214
+ // JS options (data attributes override these)
215
+ });
216
+ </script>
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 🔧 Methods
222
+
223
+ Call via jQuery plugin method:
224
+
225
+ ```javascript
226
+ // Get selected values
227
+ var values = $('#mySelect').oneSelect('getValues');
228
+ var labels = $('#mySelect').oneSelect('getLabels');
229
+
230
+ // Set selection
231
+ $('#mySelect').oneSelect('value', ['Apple', 'Banana']);
232
+
233
+ // Update data
234
+ $('#mySelect').oneSelect('updateData', ['New', 'Data']);
235
+
236
+ // Load data via AJAX
237
+ $('#mySelect').oneSelect('loadData');
238
+
239
+ // Selection control
240
+ $('#mySelect').oneSelect('selectAll');
241
+ $('#mySelect').oneSelect('unselectAll');
242
+ $('#mySelect').oneSelect('select', 'Apple');
243
+ $('#mySelect').oneSelect('unselect', 'Banana');
244
+ $('#mySelect').oneSelect('toggleSelection', 'Cherry');
245
+
246
+ // Dropdown control
247
+ $('#mySelect').oneSelect('open');
248
+ $('#mySelect').oneSelect('close');
249
+
250
+ // Get instance ID
251
+ var instanceId = $('#mySelect').oneSelect('getInstanceId');
252
+
253
+ // Get instance object
254
+ var instance = OneSelect.getInstance(instanceId);
255
+
256
+ // Get all instances
257
+ var allInstances = OneSelect.getAllInstances();
258
+
259
+ // Destroy
260
+ $('#mySelect').oneSelect('destroy');
261
+ ```
262
+
263
+ ---
264
+
265
+ ## 🎯 Callbacks
266
+
267
+ ### onChange(values, labels)
268
+
269
+ Called every time selection changes. Most important callback.
270
+
271
+ ```javascript
272
+ $('#mySelect').oneSelect({
273
+ data: ['A', 'B', 'C'],
274
+ onChange: function(values, labels) {
275
+ console.log('Values:', values);
276
+ console.log('Labels:', labels);
277
+ // values: ['A', 'C']
278
+ // labels: ['A', 'C']
279
+ }
280
+ });
281
+ ```
282
+
283
+ ### onOk(values, labels)
284
+
285
+ Called when OK button is clicked.
286
+
287
+ ```javascript
288
+ $('#mySelect').oneSelect({
289
+ onOk: function(values, labels) {
290
+ alert('Selected: ' + labels.join(', '));
291
+ }
292
+ });
293
+ ```
294
+
295
+ ### onCancel()
296
+
297
+ Called when Cancel button is clicked and clears all selections.
298
+
299
+ ```javascript
300
+ $('#mySelect').oneSelect({
301
+ onCancel: function() {
302
+ console.log('Selection cancelled');
303
+ }
304
+ });
305
+ ```
306
+
307
+ ### AJAX Callbacks
308
+
309
+ ```javascript
310
+ $('#mySelect').oneSelect({
311
+ ajax: {
312
+ url: '/api/items',
313
+ method: 'GET'
314
+ },
315
+ beforeLoad: function() {
316
+ $('#loading').show();
317
+ },
318
+ afterLoad: function(response) {
319
+ $('#loading').hide();
320
+ console.log('Data loaded:', response);
321
+ },
322
+ onLoadError: function(error) {
323
+ $('#error').text('Error: ' + error);
324
+ }
325
+ });
326
+ ```
327
+
328
+ ---
329
+
330
+ ## 📚 Examples
331
+
332
+ ### 1. Basic Usage
333
+
334
+ ```javascript
335
+ $('#mySelect').oneSelect({
336
+ placeholder: 'Select fruits...',
337
+ data: ['Apple', 'Banana', 'Cherry', 'Mango']
338
+ });
339
+ ```
340
+
341
+ ### 2. How It Works
342
+
343
+ **Data Format:** Now supports both String Array and Key-Value Object
344
+
345
+ #### Option 1: String Array (original)
346
+ ```javascript
347
+ data: ['Apple', 'Banana', 'Cherry']
348
+ ```
349
+ **Result:**
350
+ - **value** (what gets submitted): `0, 1, 2` (array indices)
351
+ - **label** (what user sees): `'Apple', 'Banana', 'Cherry'`
352
+
353
+ #### Option 2: Key-Value Object (NEW!)
354
+ ```javascript
355
+ data: {
356
+ 'fruit_1': 'Apple',
357
+ 'fruit_2': 'Banana',
358
+ 'fruit_3': 'Cherry'
359
+ }
360
+ ```
361
+ **Result:**
362
+ - **value** (what gets submitted): `'fruit_1', 'fruit_2', 'fruit_3'` (keys)
363
+ - **label** (what user sees): `'Apple', 'Banana', 'Cherry'` (values)
364
+
365
+ **Perfect for:**
366
+ - Database IDs as keys
367
+ - Backend-generated key-value pairs
368
+ - Real-world data structures
369
+
370
+ **Example with PHP data:**
371
+ ```php
372
+ // PHP
373
+ $items = [
374
+ 5 => "M.Hadi_9_6/1",
375
+ 6 => "Sarayevo_20_4/2/1",
376
+ 7 => "Sarayevo_13B_3/2/1"
377
+ ];
378
+ echo json_encode($items);
379
+ // Output: {"5":"M.Hadi_9_6/1","6":"Sarayevo_20_4/2/1","7":"Sarayevo_13B_3/2/1"}
380
+ ```
381
+
382
+ ```javascript
383
+ // JavaScript
384
+ $('#mySelect').oneSelect({
385
+ data: {"5":"M.Hadi_9_6/1", "6":"Sarayevo_20_4/2/1", "7":"Sarayevo_13B_3/2/1"}
386
+ });
387
+ // value: "5", "6", "7" (keys - submitted to form)
388
+ // label: "M.Hadi_9_6/1", "Sarayevo_20_4/2/1", "Sarayevo_13B_3/2/1" (values - displayed to user)
389
+ ```
390
+
391
+ ### 2.1. AJAX with Simple URL (NEW!)
392
+
393
+ **Easy AJAX setup with just URL:**
394
+
395
+ ```javascript
396
+ // Method 1: JavaScript configuration
397
+ $('#mySelect').oneSelect({
398
+ ajax: {
399
+ url: '/api/fruits',
400
+ method: 'GET' // Default is GET
401
+ },
402
+ autoLoad: true
403
+ });
404
+ ```
405
+
406
+ ```html
407
+ <!-- Method 2: HTML data attribute (easiest!) -->
408
+ <div id="mySelect" data-ones-ajax="/api/fruits"></div>
409
+
410
+ <script>
411
+ $('#mySelect').oneSelect();
412
+ </script>
413
+ ```
414
+
415
+ **Advanced AJAX configuration:**
416
+ ```html
417
+ <div id="mySelect"
418
+ data-ones-ajax='{"url":"/api/fruits","method":"POST","data":{"category":"fresh"}}'>
419
+ </div>
420
+ ```
421
+
422
+ ### 3. Value Parameter (Pre-selected Items)
423
+
424
+ ```javascript
425
+ // Single value (by index)
426
+ $('#mySelect').oneSelect({
427
+ data: ['Apple', 'Banana', 'Cherry', 'Mango'],
428
+ value: 0 // Index 0 ('Apple') will be selected
429
+ });
430
+
431
+ // Array with multiple selection
432
+ $('#mySelect').oneSelect({
433
+ data: ['Apple', 'Banana', 'Cherry', 'Mango'],
434
+ value: [0, 2], // Indices 0 ('Apple') and 2 ('Cherry') will be selected
435
+ showBadges: true
436
+ });
437
+
438
+ // HTML data attribute
439
+ <div class="one-select"
440
+ data-ones-data='["Apple", "Banana", "Cherry"]'
441
+ data-ones-value='"0"'> <!-- Single index -->
442
+ </div>
443
+
444
+ <!-- OR -->
445
+ <div class="one-select"
446
+ data-ones-data='["Apple", "Banana", "Cherry"]'
447
+ data-ones-value='["0", "2"]'> <!-- Multiple indices -->
448
+ </div>
449
+ ```
450
+
451
+ ### 4. Badge System
452
+
453
+ ```javascript
454
+ // Trigger badges
455
+ $('#mySelect').oneSelect({
456
+ data: ['A', 'B', 'C'],
457
+ showBadges: true
458
+ });
459
+
460
+ // External badges
461
+ <div id="badgesContainer"></div>
462
+
463
+ $('#mySelect').oneSelect({
464
+ data: ['A', 'B', 'C'],
465
+ showBadgesExternal: 'badgesContainer'
466
+ });
467
+ ```
468
+
469
+ ### 5. Form Submission
470
+
471
+ ```javascript
472
+ $('#mySelect').oneSelect({
473
+ data: ['Item 1', 'Item 2'],
474
+ name: 'items',
475
+ multiple: true,
476
+ submitForm: true,
477
+ formId: 'myForm'
478
+ });
479
+ ```
480
+
481
+ Backend example:
482
+ ```javascript
483
+ // Receive items array: [0, 1] (indices of selected items)
484
+ // Use indices to get original values from your data array
485
+ ```
486
+
487
+ ### 6. AJAX Data Loading
488
+
489
+ ```javascript
490
+ $('#mySelect').oneSelect({
491
+ ajax: {
492
+ url: '/api/categories',
493
+ method: 'GET'
494
+ },
495
+ beforeLoad: function() {
496
+ console.log('Loading...');
497
+ },
498
+ afterLoad: function(response) {
499
+ console.log('Loaded:', response);
500
+ }
501
+ });
502
+ ```
503
+
504
+ ### 7. Multiple Instances
505
+
506
+ ```javascript
507
+ // Each has independent ID
508
+ var fruits = $('#fruits').oneSelect({
509
+ data: ['Apple', 'Banana', 'Cherry']
510
+ });
511
+
512
+ var colors = $('#colors').oneSelect({
513
+ data: ['Red', 'Blue', 'Green']
514
+ });
515
+
516
+ // External control over instances
517
+ var fruitsInstance = OneSelect.getInstance(
518
+ $('#fruits').oneSelect('getInstanceId')
519
+ );
520
+ fruitsInstance.selectAll();
521
+ ```
522
+
523
+ ### 8. Search Feature
524
+
525
+ ```javascript
526
+ // Enable local search (filters existing data)
527
+ $('#mySelect').oneSelect({
528
+ data: ['Apple', 'Banana', 'Cherry', 'Mango', 'Orange', 'Grape'],
529
+ showSearch: true,
530
+ searchPlaceholder: 'Type to search...'
531
+ });
532
+
533
+ // Enable AJAX search (with debounce)
534
+ $('#mySelect').oneSelect({
535
+ showSearch: true,
536
+ searchUrl: '/api/search',
537
+ searchDebounceDelay: 500,
538
+ searchPlaceholder: 'Search items...'
539
+ });
540
+
541
+ // HTML data attribute example (local search)
542
+ <div class="one-select"
543
+ data-ones-data='["Apple", "Banana", "Cherry", "Mango"]'
544
+ data-ones-show-search="true"
545
+ data-ones-search-placeholder="Find a fruit...">
546
+ </div>
547
+
548
+ // HTML data attribute example (AJAX search)
549
+ <div class="one-select"
550
+ data-ones-show-search="true"
551
+ data-ones-search-url="/api/customers/search"
552
+ data-ones-search-debounce-delay="300"
553
+ data-ones-search-placeholder="Search customers...">
554
+ </div>
555
+ ```
556
+
557
+ **AJAX Search Server Response Format:**
558
+
559
+ The server should respond with JSON in one of these formats:
560
+
561
+ ```json
562
+ // Direct array
563
+ ["Apple", "Banana", "Cherry"]
564
+
565
+ // Wrapped with 'data'
566
+ {
567
+ "data": [{"value": 1, "label": "Apple"}, {"value": 2, "label": "Banana"}]
568
+ }
569
+
570
+ // Wrapped with 'results'
571
+ {
572
+ "results": ["Apple", "Banana"]
573
+ }
574
+ ```
575
+
576
+ The request will be sent as `GET /api/search?q=searchterm`
577
+
578
+ ---
579
+
580
+ ## 📦 Horizontal Scroll Behavior
581
+
582
+ **Automatic dropdown closing on horizontal scroll:**
583
+
584
+ The dropdown automatically closes when the user performs any horizontal scrolling action while the dropdown is open. This prevents the dropdown from appearing in the wrong position when:
585
+
586
+ - Tables with `overflow-x: auto` are scrolled horizontally
587
+ - Any scrollable container is scrolled horizontally
588
+ - Touchpad/trackpad horizontal gestures are used
589
+ - Mouse wheel horizontal scrolling is performed
590
+
591
+ ### How It Works
592
+
593
+ The plugin uses two methods to detect horizontal scroll:
594
+
595
+ 1. **Wheel Event Detection**: Detects horizontal mouse/touchpad scrolling in real-time
596
+ 2. **Periodic Scroll Checking**: Every 50ms, checks if any scrollable element's `scrollLeft` position has changed
597
+
598
+ When horizontal scroll is detected, the dropdown immediately closes.
599
+
600
+ ### Example
601
+
602
+ ```html
603
+ <div style="overflow-x: auto; width: 100%;">
604
+ <table style="width: 2000px;">
605
+ <tr>
606
+ <td>
607
+ <div id="mySelect"></div>
608
+ </td>
609
+ <td>Other columns...</td>
610
+ </tr>
611
+ </table>
612
+ </div>
613
+
614
+ <script>
615
+ $('#mySelect').oneSelect({
616
+ data: ['Apple', 'Banana', 'Cherry']
617
+ });
618
+ </script>
619
+ ```
620
+
621
+ **Behavior:**
622
+ - User opens dropdown ✅
623
+ - User scrolls table horizontally → **Dropdown closes automatically** ✅
624
+
625
+ ---
626
+
627
+ ## 🔄 AJAX Integration
628
+
629
+ ### AJAX Configuration
630
+
631
+ ```javascript
632
+ $('#mySelect').oneSelect({
633
+ ajax: {
634
+ url: '/api/items',
635
+ method: 'GET',
636
+ data: { category: 'fruits', active: true }
637
+ },
638
+ autoLoad: false
639
+ });
640
+
641
+ // Manual load
642
+ $('#loadBtn').on('click', function() {
643
+ $('#mySelect').oneSelect('loadData', {
644
+ url: '/api/different-items',
645
+ data: { filter: 'active' }
646
+ });
647
+ });
648
+ ```
649
+
650
+ ### Supported Response Formats
651
+
652
+ **1. Direct array:**
653
+ ```json
654
+ ["Apple", "Banana", "Cherry"]
655
+ ```
656
+
657
+ **2. Wrapped with 'data':**
658
+ ```json
659
+ {
660
+ "data": ["Apple", "Banana"]
661
+ }
662
+ ```
663
+
664
+ **3. Wrapped with 'results':**
665
+ ```json
666
+ {
667
+ "results": ["Apple", "Banana"]
668
+ }
669
+ ```
670
+
671
+ **4. Object array:**
672
+ ```json
673
+ [
674
+ {"value": 1, "label": "Apple"},
675
+ {"value": 2, "label": "Banana"}
676
+ ]
677
+ ```
678
+
679
+ ---
680
+
681
+ ## 📝 Form Submission
682
+
683
+ ### Hidden Inputs
684
+
685
+ Component automatically creates `<input type="hidden">` elements:
686
+
687
+ ```javascript
688
+ $('#mySelect').oneSelect({
689
+ name: 'items',
690
+ multiple: true,
691
+ data: ['A', 'B', 'C']
692
+ });
693
+ ```
694
+
695
+ HTML result:
696
+ ```html
697
+ <input type="hidden" name="items[]" value="A">
698
+ <input type="hidden" name="items[]" value="B">
699
+ <input type="hidden" name="items[]" value="C">
700
+ ```
701
+
702
+ ### data-ones-value Attribute Synchronization
703
+
704
+ The `data-ones-value` attribute automatically synchronizes with the current selection state:
705
+
706
+ **Real-time Updates:**
707
+ - Attribute updates immediately when items are selected/deselected
708
+ - Contains JSON array of selected values: `data-ones-value='["A","C"]'`
709
+ - Removed completely when no items are selected (not set to `[]` or `null`)
710
+
711
+ **Cancel Button Behavior:**
712
+ - Pressing Cancel clears all selections
713
+ - The `data-ones-value` attribute is **removed completely** from the DOM
714
+ - This ensures proper state management on page reload
715
+
716
+ **Server Integration:**
717
+ ```php
718
+ // PHP: Set initial value from POST data
719
+ $selected = isset($_POST['items']) ? json_encode($_POST['items']) : '[]';
720
+ echo '<div id="mySelect" data-ones-value=\'' . $selected . '\'></div>';
721
+ ```
722
+
723
+ **JavaScript Observation:**
724
+ ```javascript
725
+ // Watch for attribute changes (useful for debugging)
726
+ $('#mySelect').on('DOMSubtreeModified', function() {
727
+ console.log($(this).data('ones-value')); // Current value as array
728
+ });
729
+ ```
730
+
731
+ ---
732
+
733
+ ## 🏷️ Badge System
734
+
735
+ ### Trigger Badges
736
+
737
+ ```javascript
738
+ $('#mySelect').oneSelect({
739
+ showBadges: true
740
+ });
741
+ ```
742
+
743
+ Result: `[Apple ×] [Banana ×] [Cherry ×]`
744
+
745
+ ### External Badges
746
+
747
+ ```javascript
748
+ <div id="myBadges"></div>
749
+
750
+ $('#mySelect').oneSelect({
751
+ showBadgesExternal: 'myBadges'
752
+ });
753
+ ```
754
+
755
+ ### Badge Properties
756
+
757
+ - **Background:** `#3b82f6` (light blue)
758
+ - **Text color:** `#fff` (white)
759
+ - **Remove button (×):** `#fff` (white)
760
+ - **Hover:** Light gray background
761
+ - **Remove:** Clicking × button unselects item
762
+
763
+ ### Both Together
764
+
765
+ ```javascript
766
+ $('#mySelect').oneSelect({
767
+ showBadges: true, // Badge in trigger
768
+ showBadgesExternal: 'myBadges' // Badge outside as well
769
+ });
770
+ ```
771
+
772
+ ---
773
+
774
+ ## 🎨 CSS Styling
775
+
776
+ Main CSS classes with `cms-` prefix:
777
+
778
+ ```css
779
+ .one-select /* Main container (relative positioned) */
780
+ .cms-wrapper /* Wrapper (relative positioned) */
781
+ .cms-trigger /* Button that opens dropdown */
782
+ .cms-selected-text /* Selected text */
783
+ .cms-dropdown /* Dropdown menu (absolute positioned) */
784
+ .cms-search-wrapper /* Search input wrapper */
785
+ .cms-search-input /* Search input field */
786
+ .cms-options-container /* Options container */
787
+ .cms-options-container.cms-loading /* Loading state for AJAX search */
788
+ .cms-option /* Single option */
789
+ .cms-option.selected /* Selected option */
790
+ .cms-option.select-all /* "Select All" option */
791
+ .cms-badge /* Badge */
792
+ .cms-badge-remove /* Badge × button */
793
+ .cms-btn /* OK/Cancel buttons */
794
+ ```
795
+
796
+ ### DOM Structure
797
+
798
+ ```html
799
+ <!-- Wrapper element (your container) -->
800
+ <div class="one-select">
801
+ <div class="cms-wrapper">
802
+ <div class="cms-trigger">
803
+ <span class="cms-selected-text">Select...</span>
804
+ </div>
805
+ </div>
806
+ </div>
807
+
808
+ <!-- Dropdown is appended to body with dynamic positioning -->
809
+ <div class="cms-dropdown" style="position: fixed; top: ...; left: ...; width: ...;">
810
+ <!-- Search input (when showSearch is true) -->
811
+ <div class="cms-search-wrapper">
812
+ <input type="text" class="cms-search-input" placeholder="Search...">
813
+ </div>
814
+ <div class="cms-options-container">
815
+ <div class="cms-option select-all">...</div>
816
+ <div class="cms-option">...</div>
817
+ </div>
818
+ <div class="cms-buttons">
819
+ <button class="cms-btn cms-btn-ok">OK</button>
820
+ <button class="cms-btn cms-btn-cancel">Cancel</button>
821
+ </div>
822
+ </div>
823
+ ```
824
+
825
+ **Note:** The dropdown is positioned using `position: fixed` with viewport coordinates (`getBoundingClientRect()`). This ensures it stays correctly positioned even when parent elements scroll.
826
+
827
+ ### Custom CSS Example
828
+
829
+ ```css
830
+ /* Selected option styling */
831
+ .cms-option.selected label {
832
+ font-weight: 600;
833
+ color: #007bff;
834
+ }
835
+
836
+ /* Badge styling */
837
+ .cms-badge {
838
+ background: #3b82f6;
839
+ color: #fff;
840
+ border-radius: 12px;
841
+ padding: 3px 8px;
842
+ }
843
+
844
+ /* Search input styling */
845
+ .cms-search-input {
846
+ background: #f8f9fa;
847
+ border-color: #dee2e6;
848
+ }
849
+ .cms-search-input:focus {
850
+ border-color: #3b82f6;
851
+ box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
852
+ }
853
+
854
+ /* Custom dropdown width */
855
+ .one-select {
856
+ width: 400px;
857
+ }
858
+ ```
859
+
860
+ ---
861
+
862
+ ## 🚀 Quick Usage Examples
863
+
864
+ ```javascript
865
+ // 1. Simple
866
+ $('#select1').oneSelect({
867
+ data: ['A', 'B', 'C']
868
+ });
869
+
870
+ // 2. Pre-selected items (by index)
871
+ $('#select2').oneSelect({
872
+ data: ['Apple', 'Banana', 'Cherry'],
873
+ value: [0, 2] // Selects indices 0 and 2
874
+ });
875
+
876
+ // 3. Key-Value Object (NEW!)
877
+ $('#select3').oneSelect({
878
+ data: {
879
+ 'id_1': 'Apple',
880
+ 'id_2': 'Banana',
881
+ 'id_3': 'Cherry'
882
+ },
883
+ value: ['id_1', 'id_3'], // Selects by keys
884
+ name: 'fruits'
885
+ });
886
+
887
+ // 4. Badges
888
+ $('#select4').oneSelect({
889
+ data: ['X', 'Y', 'Z'],
890
+ showBadges: true
891
+ });
892
+
893
+ // 5. Form submission
894
+ $('#select5').oneSelect({
895
+ data: ['P1', 'P2'],
896
+ name: 'products',
897
+ multiple: true,
898
+ submitForm: true,
899
+ formId: 'myForm'
900
+ });
901
+
902
+ // 6. AJAX with simple URL (NEW!)
903
+ $('#select6').oneSelect({
904
+ ajax: {url: '/api/items'},
905
+ autoLoad: true
906
+ });
907
+
908
+ // OR using data attribute (easiest):
909
+ // <div id="select6" data-ones-ajax="/api/items"></div>
910
+
911
+ // 7. Click outside behavior
912
+ $('#select7').oneSelect({
913
+ closeOnOutside: true // Close when clicking outside (default)
914
+ });
915
+
916
+ // 8. Search feature (local filtering)
917
+ $('#select8').oneSelect({
918
+ data: ['Apple', 'Banana', 'Cherry', 'Mango', 'Orange'],
919
+ showSearch: true,
920
+ searchPlaceholder: 'Search fruits...'
921
+ });
922
+
923
+ // 9. AJAX search with debounce
924
+ $('#select9').oneSelect({
925
+ showSearch: true,
926
+ searchUrl: '/api/search',
927
+ searchDebounceDelay: 500
928
+ });
929
+ ```
930
+
931
+ ---
932
+
933
+ ## 📞 Support
934
+
935
+ ### Links
936
+
937
+ - **NPM Package:** [@kamranbaylarov/one-select](https://www.npmjs.com/package/@kamranbaylarov/one-select)
938
+ - **GitHub:** [KamranBeylarov/one-select](https://github.com/KamranBeylarov/one-select)
939
+ - **Issues:** [Report issues](https://github.com/KamranBeylarov/one-select/issues)
940
+
941
+ ### Installation
942
+
943
+ ```bash
944
+ # NPM
945
+ npm install @kamranbaylarov/one-select
946
+
947
+ # CDN (coming soon)
948
+ ```
949
+
950
+ ### Project Structure
951
+
952
+ ```
953
+ one-select/
954
+ ├── css/
955
+ │ └── one-select.min.css
956
+ ├── js/
957
+ │ ├── one-select.js
958
+ │ └── one-select.min.js
959
+ ├── package.json
960
+ └── README.md
961
+ ```
962
+
963
+ ### License
964
+
965
+ MIT License - Feel free to use in your projects!
966
+
967
+ ---
968
+
969
+ ## 🎯 Browser Support
970
+
971
+ - Chrome (latest)
972
+ - Firefox (latest)
973
+ - Safari (latest)
974
+ - Edge (latest)
975
+ - Opera (latest)
976
+
977
+ ---
978
+
979
+ **OneSelect** makes multi-select dropdowns simple and powerful! 🚀