@kamranbaylarov/one-select 1.1.1 → 1.2.1

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