Skip to content

Commit dde8a1f

Browse files
committed
refactor/Make list select component reusable
1 parent 7ebcd58 commit dde8a1f

File tree

6 files changed

+121
-129
lines changed

6 files changed

+121
-129
lines changed

app/controllers/modals/snippets_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def move
1818
@snippet = Snippet.find(params[:id])
1919
@current_folder_id = current_user.snippet_folders.find_by(snippet_id: params[:id])&.folder&.id
2020
@header = @current_folder_id ? 'Move Snippet' : 'File Snippet'
21-
@folders = current_user.folders
21+
@folders = current_user.folders.order(name: :asc)
2222
end
2323

2424
private
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { Controller } from 'stimulus'
2+
import axios from 'axios';
3+
import { events } from "../mixins/events";
4+
5+
export default class extends Controller {
6+
static targets = ['button', 'item', 'checkmark']
7+
static values = {
8+
selectedId: Number,
9+
originalId: Number,
10+
confirmPath: String
11+
}
12+
13+
initialize() {
14+
const csrfToken = document.querySelector("meta[name=csrf-token]").content
15+
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
16+
}
17+
18+
connect() {
19+
events(this)
20+
this.buttonTarget.disabled = true
21+
}
22+
23+
selectFolder(event) {
24+
const currentSelection = event.currentTarget
25+
const id = currentSelection.dataset.id
26+
const previousSelection = this.previouslySelected()
27+
28+
if (currentSelection === previousSelection ) { return }
29+
30+
this.reassignClasses(previousSelection, currentSelection)
31+
this.reassignCheckmark(currentSelection)
32+
this.selectedIdValue = id
33+
this.toggleButton()
34+
}
35+
36+
previouslySelected() {
37+
return this.itemTargets.find(item => {
38+
return Number(item.dataset.id) === this.selectedIdValue
39+
})
40+
}
41+
42+
reassignClasses(previousSelection, currentSelection) {
43+
previousSelection.classList.remove('move-snippet--item-selected')
44+
previousSelection.classList.add('move-snippet--item')
45+
currentSelection.classList.remove('move-snippet--item')
46+
currentSelection.classList.add('move-snippet--item-selected')
47+
}
48+
49+
reassignCheckmark(currentSelection) {
50+
const checkmark = this.checkmarkTarget
51+
const checkmarkClone = checkmark.cloneNode(true)
52+
53+
checkmark.remove()
54+
currentSelection.insertAdjacentElement("beforeend", checkmarkClone)
55+
}
56+
57+
toggleButton() {
58+
if (this.selectedIdValue === this.originalIdValue) {
59+
this.buttonTarget.disabled = true
60+
this.buttonTarget.classList.remove('button--cta-primary')
61+
this.buttonTarget.classList.add('button--disabled')
62+
} else {
63+
this.buttonTarget.disabled = false
64+
this.buttonTarget.classList.remove('button--disabled')
65+
this.buttonTarget.classList.add('button--cta-primary')
66+
}
67+
}
68+
69+
search(event) {
70+
const searchTerm = event.target.value.toLowerCase();
71+
72+
this.itemTargets.forEach(item => {
73+
const folderName = item.dataset.filterValue.toLowerCase()
74+
75+
if (folderName.includes(searchTerm)) {
76+
item.classList.remove('hidden')
77+
} else {
78+
item.classList.add('hidden')
79+
}
80+
})
81+
}
82+
83+
file() {
84+
axios.post(this.confirmPathValue, { folder_id: this.selectedIdValue }, { headers: { 'accept': 'application/json' } })
85+
.then(() => this.emitEvent('close-modal'))
86+
.catch(console.error)
87+
}
88+
}
Lines changed: 0 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,11 @@
11
import { Controller } from 'stimulus';
22
import { events } from "../mixins/events";
3-
import axios from 'axios';
4-
import CodeMirror from 'codemirror';
5-
import 'codemirror/mode/javascript/javascript.js'
6-
73

84
export default class extends Controller {
95
static targets = ["moveButton", "listItem", "errors"];
106

11-
12-
initialize() {
13-
14-
// console.log(CodeMirror)
15-
16-
// var myCodeMirror = CodeMirror(this.element, {
17-
// value: "function myScript(){return 100;}\n",
18-
// mode: "javascript"
19-
// });
20-
21-
const csrfToken = document.querySelector("meta[name=csrf-token]").content
22-
axios.defaults.headers.common['X-CSRF-Token'] = csrfToken
23-
}
24-
257
connect() {
268
events(this)
27-
28-
if (this.hasMoveButtonTarget) {
29-
this.moveButtonTarget.disabled = true
30-
}
319
}
3210

3311
update(event) {
@@ -82,92 +60,11 @@ export default class extends Controller {
8260
this.emitEvent('close-alert')
8361
}
8462

85-
modal_search(event) {
86-
const searchTerm = event.target.value.toLowerCase();
87-
88-
this.listItemTargets.forEach(item => {
89-
const folderName = item.getAttribute('data-filter-key').toLowerCase()
90-
91-
if (folderName.includes(searchTerm)) {
92-
item.classList.remove('hidden')
93-
} else {
94-
item.classList.add('hidden')
95-
}
96-
})
97-
}
98-
99-
select_folder(event) {
100-
const folderId = event.target.dataset.folderId
101-
102-
if (folderId === this.selectedFolderId ) { return }
103-
104-
const previousSelection = this.element.getElementsByClassName('move-snippet--item-selected')[0]
105-
106-
if (previousSelection) {
107-
previousSelection.classList.remove('move-snippet--item-selected')
108-
previousSelection.classList.add('move-snippet--item')
109-
const checkmark = document.getElementById('selected-checkmark')
110-
const checkmarkClone = checkmark.cloneNode(true)
111-
112-
checkmark.remove()
113-
114-
event.target.insertAdjacentElement("beforeend", checkmarkClone)
115-
} else {
116-
event.target.insertAdjacentHTML('beforeend', '<img id="selected-checkmark" src="/icons/checked.svg" width="14">')
117-
}
118-
119-
event.target.classList.add('move-snippet--item-selected')
120-
121-
this.selectedFolderId = folderId
122-
123-
if (this.selectedFolderId === this.originalFolderId) {
124-
this.moveButtonTarget.disabled = true
125-
this.moveButtonTarget.classList.remove('button--cta-primary')
126-
this.moveButtonTarget.classList.add('button--disabled')
127-
} else {
128-
this.moveButtonTarget.disabled = false
129-
this.moveButtonTarget.classList.remove('button--disabled')
130-
this.moveButtonTarget.classList.add('button--cta-primary')
131-
}
132-
}
133-
134-
file() {
135-
axios.post(this.confirmPath, { folder_id: this.selectedFolderId }, { headers: { 'accept': 'application/json' } })
136-
.then(res => {
137-
this.toast.display(res.data.message)
138-
this.emitEvent('close-modal');
139-
})
140-
.catch(error => {
141-
console.error(error)
142-
this.toast.display('Unable to file snippet')
143-
})
144-
}
145-
14663
get url() {
14764
return this.data.get('url')
14865
}
14966

150-
get selectedFolderId() {
151-
return this.data.get('selectedFolderId')
152-
}
153-
154-
get originalFolderId() {
155-
return this.data.get('originalFolderId')
156-
}
157-
15867
get toast() {
15968
return document.getElementById('toast').toast
16069
}
161-
162-
get confirmPath() {
163-
return this.data.get('confirmPath')
164-
}
165-
166-
set confirmPath(newPath) {
167-
return this.data.set('confirmPath', newPath)
168-
}
169-
170-
set selectedFolderId(folderId) {
171-
return this.data.set('selectedFolderId', folderId)
172-
}
17370
}

app/views/modals/snippets/move.html.erb

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,34 @@
11
<div
2-
data-controller="snippets"
3-
data-snippets-selected-folder-id="<%= @current_folder_id %>"
4-
data-snippets-original-folder-id="<%= @current_folder_id %>"
5-
data-snippets-confirm-path="<%= file_snippet_path(@snippet) %>"
2+
data-controller="list-select"
3+
data-list-select-selected-id-value="<%= @current_folder_id %>"
4+
data-list-select-original-id-value="<%= @current_folder_id %>"
5+
data-list-select-confirm-path-value="<%= file_snippet_path(@snippet) %>"
66
>
7-
<input data-action="keyup->snippets#modal_search" class="move-snippet--search" type="search" placeholder="Find a folder...">
8-
<div class="move-snippet--list" data-target="snippets.list">
7+
<input data-action="input->list-select#search" class="focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-sm shadow-sm" type="search" placeholder="Find a folder...">
8+
<div class="mt-4 border border-gray-300 rounded-sm w-full h-96 overflow-y-scroll">
99
<% @folders.each do |folder| %>
1010
<div
11-
data-action="click->snippets#select_folder"
12-
data-folder-id="<%= folder.id %>"
13-
data-target="snippets.listItem"
14-
data-filter-key="<%= folder.name %>"
11+
data-action="click->list-select#selectFolder"
12+
data-list-select-id="<%= folder.id %>"
13+
data-id="<%= folder.id %>"
14+
data-list-select-target="item"
15+
data-filter-value="<%= folder.name %>"
1516
class="<%= folder.id == @current_folder_id ? 'move-snippet--item-selected' : 'move-snippet--item' %>"
1617
>
1718
<span><%= folder.name %></span>
1819
<% if folder.id == @current_folder_id %>
19-
<img id="selected-checkmark" src="/icons/checked.svg" width="14">
20+
<div data-list-select-target="checkmark">
21+
<%= render partial: 'shared/icons/check', locals: { height: 5, width: 5, color: 'text-gray-500' } %>
22+
</div>
2023
<% end %>
2124
</div>
2225
<% end %>
2326
</div>
24-
<div class="move-snippet--buttons">
27+
<div class="mt-4 flex justify-end">
2528
<button data-action="click->modal#close" class="button--cancel">Cancel</button>
2629
<button
27-
data-action="click->snippets#file"
28-
data-target="snippets.moveButton"
30+
data-action="click->list-select#file"
31+
data-list-select-target="button"
2932
class="button--disabled ml-2"
3033
disabled
3134
>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<svg class="w-<%= width %> h-<%= height %> <%= color %>" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
2+
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"></path>
3+
</svg>

app/views/users/show.html.erb

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,18 +40,19 @@
4040
<div class="tabs-details">
4141
<div data-tabs-target="panel" class="<%= tabs_details_class_for_active_tab(:snippets, @tab_id) %>">
4242
<div data-controller="infinite-scroll-window" data-infinite-scroll-window-base-url="<%= user_snippets_path(@user) %>">
43-
<div style="display: flex; align-items: center; justify-content: space-between;" class="margin-top">
44-
45-
<div class="searchbar">
46-
<input autofocus
47-
type="text"
48-
placeholder="Search"
49-
data-action="input->infinite-scroll-window#search"
50-
data-infinite-scroll-window-target="input"
51-
/>
52-
<img src="/icons/search.svg">
53-
</div>
43+
5444

45+
<div class="mb-4 relative">
46+
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
47+
<%= render partial: 'shared/icons/search', locals: { width: 5, height: 5, color: 'text-gray-400' } %>
48+
</div>
49+
<input
50+
type="text"
51+
class="focus:ring-indigo-500 focus:border-indigo-500 block w-1/2 pl-10 sm:text-sm border-gray-300 rounded-sm shadow-sm"
52+
placeholder="Search snippets"
53+
data-action="input->infinite-scroll-window#search"
54+
data-infinite-scroll-window-target="input"
55+
>
5556
</div>
5657

5758
<div class="margin-top"

0 commit comments

Comments
 (0)