@kamranbaylarov/one-select 1.1.1 → 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,892 +1,979 @@
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.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! 🚀