select7 0.0.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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +74 -0
- data/Rakefile +16 -0
- data/app/assets/javascripts/select7.esm.js +215 -0
- data/app/assets/javascripts/select7.js +215 -0
- data/app/assets/stylesheets/select7.css +113 -0
- data/app/helpers/select7/form_helper.rb +54 -0
- data/app/helpers/select7/tag_helper.rb +23 -0
- data/app/views/select7/_field.html.erb +50 -0
- data/app/views/select7/_item.html.erb +3 -0
- data/lib/install/app/javascript/controllers/select7_controller.js +4 -0
- data/lib/install/app/javascript/controllers/select7_esm_controller.js +5 -0
- data/lib/install/select7.rb +48 -0
- data/lib/select7/engine.rb +26 -0
- data/lib/select7/version.rb +3 -0
- data/lib/select7.rb +5 -0
- data/lib/tasks/select7_tasks.rake +9 -0
- metadata +161 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: de12f1b5502b7b8ff49100e092b72a00b6f143b9e51fd0ea77d07a6ee4a7a2f9
|
4
|
+
data.tar.gz: 5723178cf260813f9b26be5bacf25e5a64d48b6bab738e227a2660f497b9a5b9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0061de765dd721b9bb54aecef14e0897633ac450fa1cad1ce8b4bd15a36af8318d5dfb8929277c242e594c6560dcee3b5edc1b4dc5a2363c911feea970276443
|
7
|
+
data.tar.gz: 91793c8a4fce00d561d92ce35cc1b436e84595994348bf968cb576c96bac7c11c38670e95d7db173854e6e0787442fe243e962b56117d8de98d5cf0c2e04e6ea
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2022
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
# Select7
|
2
|
+
Multiple choices selector (similar to select2, but with rails hotwire)
|
3
|
+

|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
# Gemfile
|
9
|
+
gem "rails", ">=7.0.0" # require Rails 7+
|
10
|
+
gem "stimulus-rails" # require stimulus
|
11
|
+
gem "select7"
|
12
|
+
|
13
|
+
# install
|
14
|
+
$ bundle install
|
15
|
+
$ rails select7:install
|
16
|
+
```
|
17
|
+
|
18
|
+
## Usage
|
19
|
+
|
20
|
+
### Searching with multiple choices
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
<%= form_with(url: search_projects_path) do |f| %>
|
24
|
+
<%= f.select7(:tags => [:id, :name], options: Tag.all) %>
|
25
|
+
<%= f.submit %>
|
26
|
+
<% end %>
|
27
|
+
|
28
|
+
# ==> This form will submit with `params[:tag_ids]` contains all ids of the selected tags
|
29
|
+
```
|
30
|
+
|
31
|
+
### In form: one/many-to-many relationship
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
<%= form_with(model: project) do |form| %>
|
35
|
+
# ...
|
36
|
+
<%= form.select7(:tags => [:id, :name], options: Tag.all) %>
|
37
|
+
# ...
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
# ==> This form will submit with `params[:project][:tag_ids]`
|
41
|
+
def project_params
|
42
|
+
params.require(:project).permit(tag_ids: [], )
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
### Suggestion for multiple choices selector
|
47
|
+
|
48
|
+
In case there're a very large number of choices, instead of query all choices as options for select, you could use a `suggestion`:
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
<%= form_with(model: project) do |form| %>
|
52
|
+
# ...
|
53
|
+
# assigned devs
|
54
|
+
<%= form.select7(:developers => [:id, :name], suggest: { url: search_developers_url(page_size: 10), format: :json }) %>
|
55
|
+
# ...
|
56
|
+
<% end %>
|
57
|
+
|
58
|
+
# this require an implementation of the suggestion
|
59
|
+
resources :developers do
|
60
|
+
get :search, on: :collection
|
61
|
+
end
|
62
|
+
|
63
|
+
class DevelopersController < ApplicationController
|
64
|
+
# ...
|
65
|
+
# suggest developers
|
66
|
+
def search
|
67
|
+
@developers = Developer.where("name like ?", "%#{params[:name]}%").first(params[:page_size].to_i)
|
68
|
+
respond_to do |format|
|
69
|
+
format.json { render json: @developers, layout: false }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
# ...
|
73
|
+
end
|
74
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require "bundler/setup"
|
2
|
+
|
3
|
+
load "rails/tasks/statistics.rake"
|
4
|
+
|
5
|
+
require "bundler/gem_tasks"
|
6
|
+
|
7
|
+
|
8
|
+
require 'rake/testtask'
|
9
|
+
|
10
|
+
Rake::TestTask.new(:test) do |t|
|
11
|
+
t.libs << 'test'
|
12
|
+
t.pattern = 'test/**/*_test.rb'
|
13
|
+
t.verbose = true
|
14
|
+
end
|
15
|
+
|
16
|
+
task :default => :test
|
@@ -0,0 +1,215 @@
|
|
1
|
+
export const Select7Controller = (base, debounce) =>
|
2
|
+
class extends base {
|
3
|
+
static targets = [ "selected", "input", "suggestion", "template" ]
|
4
|
+
static values = {
|
5
|
+
scope: String,
|
6
|
+
field: String,
|
7
|
+
valueAttr: String,
|
8
|
+
textAttr: String,
|
9
|
+
suggest: Object,
|
10
|
+
inputName: String,
|
11
|
+
multiple: Boolean,
|
12
|
+
nested: Boolean,
|
13
|
+
items: Array,
|
14
|
+
selectedItems: Array,
|
15
|
+
}
|
16
|
+
|
17
|
+
static formats = {
|
18
|
+
"json": "application/json",
|
19
|
+
"html": "text/html",
|
20
|
+
"turbo_stream": "text/vnd.turbo-stream.html"
|
21
|
+
}
|
22
|
+
|
23
|
+
connect() {
|
24
|
+
this.count = 0
|
25
|
+
this.timeoutId = null
|
26
|
+
this.element.addEventListener("turbo:submit-end", this.clearForm.bind(this))
|
27
|
+
if (this.hasInputTarget) {
|
28
|
+
this.inputTarget.setAttribute("autocomplete", "off")
|
29
|
+
}
|
30
|
+
|
31
|
+
this.debounceSuggest = debounce(
|
32
|
+
this.suggest.bind(this),
|
33
|
+
500,
|
34
|
+
{ 'leading': true }
|
35
|
+
)
|
36
|
+
|
37
|
+
this.selectedItems = this.hasSelectedItemsValue ? this.selectedItemsValue.map((v, n, x) => [v, n]) : []
|
38
|
+
|
39
|
+
document.addEventListener('click', this.outsideClick.bind(this))
|
40
|
+
}
|
41
|
+
|
42
|
+
disconnect() {
|
43
|
+
document.removeEventListener('click', this.outsideClick.bind(this))
|
44
|
+
}
|
45
|
+
|
46
|
+
suggest() {
|
47
|
+
if (this.suggestValue["url"]) {
|
48
|
+
this.remoteSuggest()
|
49
|
+
} else if (this.hasItemsValue) {
|
50
|
+
this.localSuggest()
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
localSuggest() {
|
55
|
+
const key = this.inputTarget.value.toLowerCase()
|
56
|
+
if (key != "") {
|
57
|
+
this.suggestionTarget.innerHTML = ""
|
58
|
+
const matchedItems = this.itemsValue.filter(([value, text, lowcaseText]) => lowcaseText.includes(key))
|
59
|
+
if (matchedItems.length > 0) {
|
60
|
+
matchedItems.forEach(([value, text, x]) => this.insertSuggestItem(value, text))
|
61
|
+
this.showSuggestion()
|
62
|
+
}
|
63
|
+
} else {
|
64
|
+
this.hideSuggestion()
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
remoteSuggest() {
|
69
|
+
const searchKey = this.inputTarget.value.replaceAll(/[^\w]/g, '')
|
70
|
+
if (searchKey.length <= 0)
|
71
|
+
return
|
72
|
+
|
73
|
+
const csrfToken = document.querySelector("[name='csrf-token']")?.content
|
74
|
+
const format = this.suggestValue["format"] || "html"
|
75
|
+
const contentType = this.suggestValue["content-type"] || this.constructor.formats[format]
|
76
|
+
const suggestQueryUrl = new URL(this.suggestValue["url"])
|
77
|
+
suggestQueryUrl.searchParams.append(this.textAttrValue, searchKey)
|
78
|
+
|
79
|
+
fetch(suggestQueryUrl, {
|
80
|
+
method: this.suggestValue["method"] || 'get',
|
81
|
+
mode: 'cors',
|
82
|
+
cache: 'no-cache',
|
83
|
+
credentials: 'same-origin',
|
84
|
+
headers: {
|
85
|
+
'Accept': contentType,
|
86
|
+
'Content-Type': contentType,
|
87
|
+
'X-CSRF-Token': csrfToken,
|
88
|
+
}
|
89
|
+
})
|
90
|
+
.then((r) => r.text())
|
91
|
+
.then((result) => {
|
92
|
+
if (result) {
|
93
|
+
this.suggestionTarget.innerHTML = ""
|
94
|
+
if (this.suggestValue["format"] == "json") {
|
95
|
+
const items = JSON.parse(result)
|
96
|
+
if (items.length > 0) {
|
97
|
+
items.forEach(item => {
|
98
|
+
this.insertSuggestItem(item[this.valueAttrValue], item[this.textAttrValue])
|
99
|
+
})
|
100
|
+
this.showSuggestion()
|
101
|
+
}
|
102
|
+
} else {
|
103
|
+
this.suggestionTarget.innerHTML = result
|
104
|
+
this.showSuggestion()
|
105
|
+
}
|
106
|
+
} else {
|
107
|
+
this.hideSuggestion()
|
108
|
+
}
|
109
|
+
})
|
110
|
+
}
|
111
|
+
|
112
|
+
insertSuggestItem(value, text) {
|
113
|
+
const displayText = this.selectedItems.find(item => item[0] == value) ? `✓ ${text}` : text
|
114
|
+
const optionItem = document.createElement("div")
|
115
|
+
optionItem.setAttribute("value", value)
|
116
|
+
optionItem.setAttribute("data-action", "click->select7#selectTag")
|
117
|
+
optionItem.setAttribute("class", "select7-option-item")
|
118
|
+
optionItem.innerText = displayText
|
119
|
+
|
120
|
+
this.suggestionTarget.appendChild(optionItem)
|
121
|
+
}
|
122
|
+
|
123
|
+
selectTag(e) {
|
124
|
+
const selectedView = e.target
|
125
|
+
const value = selectedView.getAttribute("value")
|
126
|
+
const name = this.inputNameValue.replace("[?]", `[${this.count++}]`)
|
127
|
+
|
128
|
+
if (!this.selectedItems.find(item => item[0] == value)) {
|
129
|
+
this.selectedItems.push([value, name])
|
130
|
+
|
131
|
+
const input = document.createElement("input")
|
132
|
+
input.setAttribute("type", "hidden")
|
133
|
+
input.setAttribute("value", value)
|
134
|
+
input.setAttribute("name", name)
|
135
|
+
|
136
|
+
const selectedItem = this.templateTarget.cloneNode(true)
|
137
|
+
selectedItem.appendChild(input)
|
138
|
+
selectedItem.insertAdjacentHTML("afterbegin", selectedView.innerHTML)
|
139
|
+
selectedItem.classList.remove("select7-hidden")
|
140
|
+
this.selectedTarget.appendChild(selectedItem)
|
141
|
+
|
142
|
+
this.emitChangedEvent("add", name, value)
|
143
|
+
}
|
144
|
+
|
145
|
+
this.hideSuggestion()
|
146
|
+
|
147
|
+
this.inputTarget.value = ""
|
148
|
+
if (!this.multipleValue) {
|
149
|
+
this.inputTarget.classList.add("select7-invisible")
|
150
|
+
}
|
151
|
+
this.inputTarget.focus()
|
152
|
+
}
|
153
|
+
|
154
|
+
removeTag(e) {
|
155
|
+
const removeView = e.target.parentElement
|
156
|
+
const name = removeView.getAttribute("data-remove-id")
|
157
|
+
const value = removeView.getAttribute("data-remove-value")
|
158
|
+
|
159
|
+
this.selectedItems = this.selectedItems.filter((_value, _name) => _name == name && value == _value)
|
160
|
+
|
161
|
+
if (removeView.hasAttribute("data-remove-id")) {
|
162
|
+
const input = document.createElement("input")
|
163
|
+
input.setAttribute("type", "hidden")
|
164
|
+
input.setAttribute("name", name)
|
165
|
+
input.setAttribute("value", value)
|
166
|
+
|
167
|
+
this.selectedTarget.appendChild(input)
|
168
|
+
removeView.querySelectorAll('input').forEach(v => this.selectedTarget.appendChild(v))
|
169
|
+
}
|
170
|
+
|
171
|
+
const input = removeView.querySelector('input')
|
172
|
+
this.emitChangedEvent("remove", name, value)
|
173
|
+
|
174
|
+
this.selectedTarget.removeChild(removeView)
|
175
|
+
this.inputTarget.classList.remove("select7-invisible")
|
176
|
+
}
|
177
|
+
|
178
|
+
showSuggestion() {
|
179
|
+
this.suggestionTarget.classList.remove("select7-hidden")
|
180
|
+
this.suggestionTarget.scrollTo(0, 0)
|
181
|
+
}
|
182
|
+
|
183
|
+
hideSuggestion() {
|
184
|
+
this.suggestionTarget.classList.add("select7-hidden")
|
185
|
+
}
|
186
|
+
|
187
|
+
clearForm() {
|
188
|
+
this.selectedTarget.innerHTML = ""
|
189
|
+
}
|
190
|
+
|
191
|
+
emitChangedEvent(action, name, value) {
|
192
|
+
const changedEvent = new CustomEvent('select7-changed', {
|
193
|
+
detail: {
|
194
|
+
scope: this.scopeValue,
|
195
|
+
field: this.fieldValue,
|
196
|
+
action: action,
|
197
|
+
change_value: value,
|
198
|
+
values: this.selectedItems.map(item => item[0])
|
199
|
+
}
|
200
|
+
})
|
201
|
+
window.dispatchEvent(changedEvent)
|
202
|
+
}
|
203
|
+
|
204
|
+
handleKeyUp(event) {
|
205
|
+
if (event.code == "Escape") {
|
206
|
+
this.hideSuggestion()
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
outsideClick(event) {
|
211
|
+
if (!event.composedPath().includes(this.element)) {
|
212
|
+
this.hideSuggestion()
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
@@ -0,0 +1,215 @@
|
|
1
|
+
const Select7Controller = (base, debounce) =>
|
2
|
+
class extends base {
|
3
|
+
static targets = [ "selected", "input", "suggestion", "template" ]
|
4
|
+
static values = {
|
5
|
+
scope: String,
|
6
|
+
field: String,
|
7
|
+
valueAttr: String,
|
8
|
+
textAttr: String,
|
9
|
+
suggest: Object,
|
10
|
+
inputName: String,
|
11
|
+
multiple: Boolean,
|
12
|
+
nested: Boolean,
|
13
|
+
items: Array,
|
14
|
+
selectedItems: Array,
|
15
|
+
}
|
16
|
+
|
17
|
+
static formats = {
|
18
|
+
"json": "application/json",
|
19
|
+
"html": "text/html",
|
20
|
+
"turbo_stream": "text/vnd.turbo-stream.html"
|
21
|
+
}
|
22
|
+
|
23
|
+
connect() {
|
24
|
+
this.count = 0
|
25
|
+
this.timeoutId = null
|
26
|
+
this.element.addEventListener("turbo:submit-end", this.clearForm.bind(this))
|
27
|
+
if (this.hasInputTarget) {
|
28
|
+
this.inputTarget.setAttribute("autocomplete", "off")
|
29
|
+
}
|
30
|
+
|
31
|
+
this.debounceSuggest = debounce(
|
32
|
+
this.suggest.bind(this),
|
33
|
+
500,
|
34
|
+
{ 'leading': true }
|
35
|
+
)
|
36
|
+
|
37
|
+
this.selectedItems = this.hasSelectedItemsValue ? this.selectedItemsValue.map((v, n, x) => [v, n]) : []
|
38
|
+
|
39
|
+
document.addEventListener('click', this.outsideClick.bind(this))
|
40
|
+
}
|
41
|
+
|
42
|
+
disconnect() {
|
43
|
+
document.removeEventListener('click', this.outsideClick.bind(this))
|
44
|
+
}
|
45
|
+
|
46
|
+
suggest() {
|
47
|
+
if (this.suggestValue["url"]) {
|
48
|
+
this.remoteSuggest()
|
49
|
+
} else if (this.hasItemsValue) {
|
50
|
+
this.localSuggest()
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
localSuggest() {
|
55
|
+
const key = this.inputTarget.value.toLowerCase()
|
56
|
+
if (key != "") {
|
57
|
+
this.suggestionTarget.innerHTML = ""
|
58
|
+
const matchedItems = this.itemsValue.filter(([value, text, lowcaseText]) => lowcaseText.includes(key))
|
59
|
+
if (matchedItems.length > 0) {
|
60
|
+
matchedItems.forEach(([value, text, x]) => this.insertSuggestItem(value, text))
|
61
|
+
this.showSuggestion()
|
62
|
+
}
|
63
|
+
} else {
|
64
|
+
this.hideSuggestion()
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
remoteSuggest() {
|
69
|
+
const searchKey = this.inputTarget.value.replaceAll(/[^\w]/g, '')
|
70
|
+
if (searchKey.length <= 0)
|
71
|
+
return
|
72
|
+
|
73
|
+
const csrfToken = document.querySelector("[name='csrf-token']")?.content
|
74
|
+
const format = this.suggestValue["format"] || "html"
|
75
|
+
const contentType = this.suggestValue["content-type"] || this.constructor.formats[format]
|
76
|
+
const suggestQueryUrl = new URL(this.suggestValue["url"])
|
77
|
+
suggestQueryUrl.searchParams.append(this.textAttrValue, searchKey)
|
78
|
+
|
79
|
+
fetch(suggestQueryUrl, {
|
80
|
+
method: this.suggestValue["method"] || 'get',
|
81
|
+
mode: 'cors',
|
82
|
+
cache: 'no-cache',
|
83
|
+
credentials: 'same-origin',
|
84
|
+
headers: {
|
85
|
+
'Accept': contentType,
|
86
|
+
'Content-Type': contentType,
|
87
|
+
'X-CSRF-Token': csrfToken,
|
88
|
+
}
|
89
|
+
})
|
90
|
+
.then((r) => r.text())
|
91
|
+
.then((result) => {
|
92
|
+
if (result) {
|
93
|
+
this.suggestionTarget.innerHTML = ""
|
94
|
+
if (this.suggestValue["format"] == "json") {
|
95
|
+
const items = JSON.parse(result)
|
96
|
+
if (items.length > 0) {
|
97
|
+
items.forEach(item => {
|
98
|
+
this.insertSuggestItem(item[this.valueAttrValue], item[this.textAttrValue])
|
99
|
+
})
|
100
|
+
this.showSuggestion()
|
101
|
+
}
|
102
|
+
} else {
|
103
|
+
this.suggestionTarget.innerHTML = result
|
104
|
+
this.showSuggestion()
|
105
|
+
}
|
106
|
+
} else {
|
107
|
+
this.hideSuggestion()
|
108
|
+
}
|
109
|
+
})
|
110
|
+
}
|
111
|
+
|
112
|
+
insertSuggestItem(value, text) {
|
113
|
+
const displayText = this.selectedItems.find(item => item[0] == value) ? `✓ ${text}` : text
|
114
|
+
const optionItem = document.createElement("div")
|
115
|
+
optionItem.setAttribute("value", value)
|
116
|
+
optionItem.setAttribute("data-action", "click->select7#selectTag")
|
117
|
+
optionItem.setAttribute("class", "select7-option-item")
|
118
|
+
optionItem.innerText = displayText
|
119
|
+
|
120
|
+
this.suggestionTarget.appendChild(optionItem)
|
121
|
+
}
|
122
|
+
|
123
|
+
selectTag(e) {
|
124
|
+
const selectedView = e.target
|
125
|
+
const value = selectedView.getAttribute("value")
|
126
|
+
const name = this.inputNameValue.replace("[?]", `[${this.count++}]`)
|
127
|
+
|
128
|
+
if (!this.selectedItems.find(item => item[0] == value)) {
|
129
|
+
this.selectedItems.push([value, name])
|
130
|
+
|
131
|
+
const input = document.createElement("input")
|
132
|
+
input.setAttribute("type", "hidden")
|
133
|
+
input.setAttribute("value", value)
|
134
|
+
input.setAttribute("name", name)
|
135
|
+
|
136
|
+
const selectedItem = this.templateTarget.cloneNode(true)
|
137
|
+
selectedItem.appendChild(input)
|
138
|
+
selectedItem.insertAdjacentHTML("afterbegin", selectedView.innerHTML)
|
139
|
+
selectedItem.classList.remove("select7-hidden")
|
140
|
+
this.selectedTarget.appendChild(selectedItem)
|
141
|
+
|
142
|
+
this.emitChangedEvent("add", name, value)
|
143
|
+
}
|
144
|
+
|
145
|
+
this.hideSuggestion()
|
146
|
+
|
147
|
+
this.inputTarget.value = ""
|
148
|
+
if (!this.multipleValue) {
|
149
|
+
this.inputTarget.classList.add("select7-invisible")
|
150
|
+
}
|
151
|
+
this.inputTarget.focus()
|
152
|
+
}
|
153
|
+
|
154
|
+
removeTag(e) {
|
155
|
+
const removeView = e.target.parentElement
|
156
|
+
const name = removeView.getAttribute("data-remove-id")
|
157
|
+
const value = removeView.getAttribute("data-remove-value")
|
158
|
+
|
159
|
+
this.selectedItems = this.selectedItems.filter((_value, _name) => _name == name && value == _value)
|
160
|
+
|
161
|
+
if (removeView.hasAttribute("data-remove-id")) {
|
162
|
+
const input = document.createElement("input")
|
163
|
+
input.setAttribute("type", "hidden")
|
164
|
+
input.setAttribute("name", name)
|
165
|
+
input.setAttribute("value", value)
|
166
|
+
|
167
|
+
this.selectedTarget.appendChild(input)
|
168
|
+
removeView.querySelectorAll('input').forEach(v => this.selectedTarget.appendChild(v))
|
169
|
+
}
|
170
|
+
|
171
|
+
const input = removeView.querySelector('input')
|
172
|
+
this.emitChangedEvent("remove", name, value)
|
173
|
+
|
174
|
+
this.selectedTarget.removeChild(removeView)
|
175
|
+
this.inputTarget.classList.remove("select7-invisible")
|
176
|
+
}
|
177
|
+
|
178
|
+
showSuggestion() {
|
179
|
+
this.suggestionTarget.classList.remove("select7-hidden")
|
180
|
+
this.suggestionTarget.scrollTo(0, 0)
|
181
|
+
}
|
182
|
+
|
183
|
+
hideSuggestion() {
|
184
|
+
this.suggestionTarget.classList.add("select7-hidden")
|
185
|
+
}
|
186
|
+
|
187
|
+
clearForm() {
|
188
|
+
this.selectedTarget.innerHTML = ""
|
189
|
+
}
|
190
|
+
|
191
|
+
emitChangedEvent(action, name, value) {
|
192
|
+
const changedEvent = new CustomEvent('select7-changed', {
|
193
|
+
detail: {
|
194
|
+
scope: this.scopeValue,
|
195
|
+
field: this.fieldValue,
|
196
|
+
action: action,
|
197
|
+
change_value: value,
|
198
|
+
values: this.selectedItems.map(item => item[0])
|
199
|
+
}
|
200
|
+
})
|
201
|
+
window.dispatchEvent(changedEvent)
|
202
|
+
}
|
203
|
+
|
204
|
+
handleKeyUp(event) {
|
205
|
+
if (event.code == "Escape") {
|
206
|
+
this.hideSuggestion()
|
207
|
+
}
|
208
|
+
}
|
209
|
+
|
210
|
+
outsideClick(event) {
|
211
|
+
if (!event.composedPath().includes(this.element)) {
|
212
|
+
this.hideSuggestion()
|
213
|
+
}
|
214
|
+
}
|
215
|
+
}
|
@@ -0,0 +1,113 @@
|
|
1
|
+
.select7-container {
|
2
|
+
position: relative;
|
3
|
+
margin-top: 1.25rem;
|
4
|
+
margin-bottom: 1.25rem;
|
5
|
+
font-size: 1.0rem;
|
6
|
+
width: 90%;
|
7
|
+
}
|
8
|
+
|
9
|
+
.select7-selected-container {
|
10
|
+
display: flex;
|
11
|
+
justify-content: flex-start;
|
12
|
+
flex-wrap: wrap;
|
13
|
+
height: fit-content;
|
14
|
+
}
|
15
|
+
|
16
|
+
.select7-selection {
|
17
|
+
margin-top: 0.5rem;
|
18
|
+
display: flex;
|
19
|
+
justify-content: flex-start;
|
20
|
+
flex-wrap: wrap;
|
21
|
+
height: fit-content;
|
22
|
+
border: solid rgb(229 231 235 / var(--tw-border-opacity));
|
23
|
+
border-width: 1px;
|
24
|
+
--tw-border-opacity: 1;
|
25
|
+
padding-top: 0.5rem;
|
26
|
+
padding-bottom: 0.5rem;
|
27
|
+
padding-left: 0.75rem;
|
28
|
+
padding-right: 0.75rem;
|
29
|
+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
30
|
+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
31
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
32
|
+
outline: 2px solid transparent;
|
33
|
+
outline-offset: 2px;
|
34
|
+
}
|
35
|
+
|
36
|
+
.select7-selected {
|
37
|
+
display: flex;
|
38
|
+
justify-content: flex-start;
|
39
|
+
}
|
40
|
+
|
41
|
+
.select7-input {
|
42
|
+
border-style: none;
|
43
|
+
outline: 2px solid transparent;
|
44
|
+
outline-offset: 2px;
|
45
|
+
font-size: 1.0rem;
|
46
|
+
}
|
47
|
+
|
48
|
+
.select7-suggestion {
|
49
|
+
position: absolute;
|
50
|
+
left: 0px;
|
51
|
+
z-index: 50;
|
52
|
+
height: 6rem;
|
53
|
+
width: 100%;
|
54
|
+
overflow-y: auto;
|
55
|
+
overflow-x: hidden;
|
56
|
+
margin-top: -2px;
|
57
|
+
border: solid rgb(229 231 235 / var(--tw-border-opacity));
|
58
|
+
border-width: 0 1px 2px 1px;
|
59
|
+
--tw-border-opacity: 1;
|
60
|
+
--tw-bg-opacity: 1;
|
61
|
+
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
62
|
+
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
63
|
+
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
64
|
+
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
65
|
+
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
66
|
+
}
|
67
|
+
|
68
|
+
.select7-option-item {
|
69
|
+
width: 100%;
|
70
|
+
padding-left: 0.5rem;
|
71
|
+
padding-right: 0.5rem;
|
72
|
+
padding-top: 0.25rem;
|
73
|
+
padding-bottom: 0.25rem;
|
74
|
+
}
|
75
|
+
|
76
|
+
.select7-option-item:hover {
|
77
|
+
background-color: rgb(229 231 235);
|
78
|
+
cursor: pointer;
|
79
|
+
}
|
80
|
+
|
81
|
+
.select7-selected-item {
|
82
|
+
margin-right: 0.25rem;
|
83
|
+
display: flex;
|
84
|
+
align-items: center;
|
85
|
+
justify-content: space-between;
|
86
|
+
border-radius: 0.2rem;
|
87
|
+
padding: 0.25rem;
|
88
|
+
background-color: #d9e1e1;
|
89
|
+
}
|
90
|
+
|
91
|
+
.select7-item-close {
|
92
|
+
padding: 0.25rem;
|
93
|
+
margin-left: 0.25rem;
|
94
|
+
text-align: center;
|
95
|
+
line-height: 1.0rem;
|
96
|
+
}
|
97
|
+
|
98
|
+
.select7-item-close:hover {
|
99
|
+
background-color: rgb(242, 242, 244);
|
100
|
+
cursor: pointer;
|
101
|
+
}
|
102
|
+
|
103
|
+
.select7-hidden {
|
104
|
+
display: none !important;
|
105
|
+
}
|
106
|
+
|
107
|
+
.select7-invisible {
|
108
|
+
visibility: hidden;
|
109
|
+
}
|
110
|
+
|
111
|
+
.select7-visible {
|
112
|
+
visibility: visible;
|
113
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Select7::FormHelper
|
2
|
+
class ActionView::Helpers::FormBuilder
|
3
|
+
include Select7::TagHelper
|
4
|
+
|
5
|
+
def select7(options: [], selecteds: [], suggest: {}, multiple: true, params: @template.params, **attributes)
|
6
|
+
field, (value_attr, text_attr) = attributes.first
|
7
|
+
|
8
|
+
input_name = if scope = @object_name then
|
9
|
+
"#{scope}[#{field.to_s.singularize}_#{value_attr}#{multiple ? 's' : ''}]" + (multiple ? "[]" : "")
|
10
|
+
else
|
11
|
+
"#{field.to_s.singularize}_#{value_attr}" + (multiple ? "s[]" : "")
|
12
|
+
end
|
13
|
+
|
14
|
+
selecteds = (if @object then
|
15
|
+
@object.send(field)
|
16
|
+
else
|
17
|
+
clazz = field.to_s.classify.constantize
|
18
|
+
Array(clazz.where(value_attr.to_sym => params["#{field.to_s.singularize}_#{value_attr}s"]).all)
|
19
|
+
end).map { |item|
|
20
|
+
[item.send(value_attr), item.send(text_attr)]
|
21
|
+
}
|
22
|
+
|
23
|
+
options_for_select = attributes[:options_for_select] || options.map { |item| [item.send(value_attr), item.send(text_attr)] }
|
24
|
+
|
25
|
+
select7_tag(
|
26
|
+
**attributes,
|
27
|
+
options_for_select: options_for_select,
|
28
|
+
selecteds: selecteds,
|
29
|
+
suggest: suggest,
|
30
|
+
scope: @object_name,
|
31
|
+
input_name: input_name,
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: REMOVE IF NO USECASE
|
36
|
+
# def select7_fields_for(record_name, field = "id", option_items: [], selected_items: [], suggest: {}, **attributes)
|
37
|
+
# nested_attributes = nested_attributes_association?(record_name)
|
38
|
+
# association = nested_attributes ? "#{record_name}_attributes" : record_name
|
39
|
+
# scope = "#{@object_name}[#{association}]"
|
40
|
+
# input_name = "#{scope}[?][#{field}]"
|
41
|
+
|
42
|
+
# select7_tag(
|
43
|
+
# field,
|
44
|
+
# option_items,
|
45
|
+
# selected_items: selected_items,
|
46
|
+
# suggest: suggest,
|
47
|
+
# scope: scope,
|
48
|
+
# input_name: input_name,
|
49
|
+
# nested_attributes: nested_attributes,
|
50
|
+
# **attributes
|
51
|
+
# )
|
52
|
+
# end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Select7::TagHelper
|
2
|
+
def select7_tag(options_for_select: [], selecteds: [], suggest: {}, **attributes)
|
3
|
+
field, (value_attr, text_attr) = attributes.first
|
4
|
+
options_for_select.map! {|(value, text)| [value, text, text.downcase] }
|
5
|
+
attributes.reverse_merge!(css: {}, multiple: true, nested_attributes: nil)
|
6
|
+
attributes[:input_name] ||= "#{field}" + (attributes[:multiple] ? "[]" : "")
|
7
|
+
|
8
|
+
@template.render partial: "select7/field",
|
9
|
+
locals: {
|
10
|
+
field: field,
|
11
|
+
value_attr: value_attr,
|
12
|
+
text_attr: text_attr,
|
13
|
+
selected_items: selecteds,
|
14
|
+
option_items: options_for_select,
|
15
|
+
suggest: suggest || {},
|
16
|
+
**attributes
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def select7_item_tag(id, content = nil)
|
21
|
+
render partial: "select7/item", locals: {id: id, content: content || yield.html_safe }
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
<div class="<%= css[:field_container] || 'select7-container' %>"
|
2
|
+
data-controller="select7"
|
3
|
+
data-action="keyup@document->select7#handleKeyUp"
|
4
|
+
data-select7-scope-value="<%= scope %>"
|
5
|
+
data-select7-field-value="<%= field %>"
|
6
|
+
data-select7-value-attr-value="<%= value_attr %>"
|
7
|
+
data-select7-text-attr-value="<%= text_attr %>"
|
8
|
+
data-select7-input-name-value="<%= input_name %>"
|
9
|
+
data-select7-multiple-value="<%= multiple %>"
|
10
|
+
data-select7-nested-value="<%= nested_attributes.nil? %>"
|
11
|
+
data-select7-suggest-value="<%= ActiveSupport::JSON.encode(suggest) %>"
|
12
|
+
data-select7-items-value="<%= ActiveSupport::JSON.encode(option_items) %>"
|
13
|
+
data-select7-selected-items-value="<%= ActiveSupport::JSON.encode(selected_items) %>"
|
14
|
+
>
|
15
|
+
|
16
|
+
<div data-select7-target="template" class="select7-hidden <%= css[:selected_item] || 'select7-selected-item' %>">
|
17
|
+
<span class="<%= css[:selected_item_close] || 'select7-item-close' %>" data-action="click->select7#removeTag">×</span>
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<div class="<%= css[:selection] || 'select7-selection' %>">
|
21
|
+
<div data-select7-target="selected" class="<%= css[:selected_container] || 'select7-selected-container' %>">
|
22
|
+
<% if multiple %>
|
23
|
+
<% selected_items.each do |id, text, _| %>
|
24
|
+
<div class="<%= css[:selected_item] || 'select7-selected-item' %>"
|
25
|
+
<% if nested_attributes %>
|
26
|
+
data-remove-id=<%= "#{scope}[#{id}][_destroy]" %>
|
27
|
+
data-remove-value="true"
|
28
|
+
<% end %>
|
29
|
+
>
|
30
|
+
|
31
|
+
<% if nested_attributes %>
|
32
|
+
<input type="hidden" name=<%= "#{scope}[#{item.id}][id]" %> value="<%= id %>">
|
33
|
+
<input type="hidden" name=<%= "#{scope}[#{item.id}][#{field}]" %> value="<%= id %>">
|
34
|
+
<% else %>
|
35
|
+
<input type="hidden" name="<%= input_name %>" value="<%= id %>">
|
36
|
+
<% end %>
|
37
|
+
|
38
|
+
<span class="<%= css[:selected_item_content] || 'select7-selected-item-content' %>"><%= text %></span>
|
39
|
+
<span class="<%= css[:selected_item_close] || 'select7-item-close' %>" data-action="click->select7#removeTag">x</span>
|
40
|
+
</div>
|
41
|
+
<% end %>
|
42
|
+
<% else %>
|
43
|
+
<span class="<%= css[:selected_item_content] || 'select7-selected-item-content' %>"><%= selected_items.first %></span>
|
44
|
+
<% end %>
|
45
|
+
</div>
|
46
|
+
<input data-select7-target="input" data-action="input->select7#debounceSuggest" class="<%= css[:input] || 'select7-input' %>" autocomplete="off" placeholder="<%= field %>">
|
47
|
+
</div>
|
48
|
+
<div data-select7-target="suggestion" class="select7-hidden <%= css[:suggestion] || 'select7-suggestion' %>">
|
49
|
+
</div>
|
50
|
+
</div>
|
@@ -0,0 +1,48 @@
|
|
1
|
+
IMPORTMAP_PATH = Rails.root.join("config/importmap.rb")
|
2
|
+
APPLICATION_LAYOUT_PATH = Rails.root.join("app/views/layouts/application.html.erb")
|
3
|
+
|
4
|
+
say "Import Select7 Js"
|
5
|
+
|
6
|
+
if IMPORTMAP_PATH.exist?
|
7
|
+
append_to_file IMPORTMAP_PATH, %(\npin "lodash.debounce", to: "https://cdn.jsdelivr.net/npm/lodash.debounce@4.0.8/+esm", preload: true\n)
|
8
|
+
append_to_file IMPORTMAP_PATH, %(pin "select7", to: "select7.esm.js", preload: true\n)
|
9
|
+
|
10
|
+
copy_file "#{__dir__}/app/javascript/controllers/select7_esm_controller.js", "app/javascript/controllers/select7_controller.js"
|
11
|
+
|
12
|
+
elsif Rails.root.join("package.json").exist?
|
13
|
+
if APPLICATION_LAYOUT_PATH.exist?
|
14
|
+
run "yarn add lodash.debounce"
|
15
|
+
|
16
|
+
insert_into_file APPLICATION_LAYOUT_PATH.to_s, <<~ERB.indent(4), before: /\s*<\/head/
|
17
|
+
\n<%= javascript_include_tag "select7", "data-turbo-track": "reload", rel: :preload, async: true %>
|
18
|
+
ERB
|
19
|
+
|
20
|
+
append_to_file Rails.root.join("app/javascript/controllers/index.js"), <<~ERB
|
21
|
+
import Select7Controller from "./select7_controller.js"
|
22
|
+
application.register("select7", Select7Controller)
|
23
|
+
ERB
|
24
|
+
|
25
|
+
append_to_file Rails.root.join("app/javascript/application.js"), <<~ERB
|
26
|
+
ERB
|
27
|
+
|
28
|
+
copy_file "#{__dir__}/app/javascript/controllers/select7_controller.js", "app/javascript/controllers/select7_controller.js"
|
29
|
+
else
|
30
|
+
say %(Couldn't Import Select7 Js), :red
|
31
|
+
end
|
32
|
+
else
|
33
|
+
say %(You must either be running with node (package.json) or importmap-rails (config/importmap.rb) to use this gem.), :red
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
# ==============
|
38
|
+
|
39
|
+
say "Import Select7 Css"
|
40
|
+
if (Rails.root.join("app/assets/stylesheets/application.css")).exist?
|
41
|
+
append_to_file "app/assets/stylesheets/application.css", %(\n@import "select7.css"\n)
|
42
|
+
elsif APPLICATION_LAYOUT_PATH.exist?
|
43
|
+
insert_into_file APPLICATION_LAYOUT_PATH.to_s, <<~ERB.indent(4), before: /^\s*<%= stylesheet_link_tag/
|
44
|
+
<%= stylesheet_link_tag "select7", "select7", "data-turbo-track": "reload" %>
|
45
|
+
ERB
|
46
|
+
else
|
47
|
+
say %(Couldn't Import Select7 Css), :red
|
48
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Select7
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Select7
|
4
|
+
|
5
|
+
config.eager_load_namespaces << Select7
|
6
|
+
config.autoload_once_paths = %W(
|
7
|
+
#{root}/app/helpers
|
8
|
+
)
|
9
|
+
|
10
|
+
initializer "select7.assets" do |app|
|
11
|
+
if app.config.respond_to?(:assets)
|
12
|
+
Rails.application.config.assets.precompile += %w( select7.js select7.esm.js select7.css )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# initializer "select7.importmap", after: "importmap" do |app|
|
17
|
+
# app.config.importmap.paths << Engine.root.join("config/importmap.rb")
|
18
|
+
# end
|
19
|
+
|
20
|
+
initializer "select7.helpers", before: :load_config_initializers do
|
21
|
+
ActiveSupport.on_load(:action_controller_base) do
|
22
|
+
helper Select7::Engine.helpers
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/select7.rb
ADDED
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: select7
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lam Phan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-03-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rails
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: stimulus-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: minitest
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: capybara
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '3.26'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.26'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: selenium-webdriver
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 4.8.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 4.8.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: webdrivers
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 5.0.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 5.0.0
|
111
|
+
description: Multiple choices selector (similar to select2, but with rails hotwire)
|
112
|
+
email:
|
113
|
+
- theforestvn88@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- MIT-LICENSE
|
119
|
+
- README.md
|
120
|
+
- Rakefile
|
121
|
+
- app/assets/javascripts/select7.esm.js
|
122
|
+
- app/assets/javascripts/select7.js
|
123
|
+
- app/assets/stylesheets/select7.css
|
124
|
+
- app/helpers/select7/form_helper.rb
|
125
|
+
- app/helpers/select7/tag_helper.rb
|
126
|
+
- app/views/select7/_field.html.erb
|
127
|
+
- app/views/select7/_item.html.erb
|
128
|
+
- lib/install/app/javascript/controllers/select7_controller.js
|
129
|
+
- lib/install/app/javascript/controllers/select7_esm_controller.js
|
130
|
+
- lib/install/select7.rb
|
131
|
+
- lib/select7.rb
|
132
|
+
- lib/select7/engine.rb
|
133
|
+
- lib/select7/version.rb
|
134
|
+
- lib/tasks/select7_tasks.rake
|
135
|
+
homepage: https://github.com/theforestvn88/rails-select7.git
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata:
|
139
|
+
homepage_uri: https://github.com/theforestvn88/rails-select7.git
|
140
|
+
source_code_uri: https://github.com/theforestvn88/rails-select7.git
|
141
|
+
changelog_uri: https://github.com/theforestvn88/rails-select7.git
|
142
|
+
post_install_message:
|
143
|
+
rdoc_options: []
|
144
|
+
require_paths:
|
145
|
+
- lib
|
146
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
152
|
+
requirements:
|
153
|
+
- - ">="
|
154
|
+
- !ruby/object:Gem::Version
|
155
|
+
version: '0'
|
156
|
+
requirements: []
|
157
|
+
rubygems_version: 3.5.4
|
158
|
+
signing_key:
|
159
|
+
specification_version: 4
|
160
|
+
summary: Multiple choices selector (similar to select2, but with rails hotwire)
|
161
|
+
test_files: []
|