Skip to content

Commit

Permalink
Add sorting for key value fields (#3299)
Browse files Browse the repository at this point in the history
* Add sorting for key value fields

* Get rid of duplicated code blocks

* Simplify updown buttons

* Simplify code

* Not need Standart Error raise

* Update spec/dummy/config/database.yml

Co-authored-by: Paul Bob <[email protected]>

* Update updown buttons place

* add dnd

* fix dnd

* Update app/javascript/js/controllers/fields/key_value_controller.js

* reorder_text & DRY getters

---------

Co-authored-by: Paul Bob <[email protected]>
Co-authored-by: Adrian Marin <[email protected]>
Co-authored-by: Paul Bob <[email protected]>
  • Loading branch information
4 people authored Nov 1, 2024
1 parent a498256 commit 08269fc
Show file tree
Hide file tree
Showing 17 changed files with 113 additions and 48 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ Please read the [RELEASE.MD](./RELEASE.MD)
</tr>
</table>

[Become a sponsor ](https://github.com/sponsors/adrianthedev)
[Become a sponsor](mailto:[email protected])


![Alt](https://repobeats.axiom.co/api/embed/1481a6a259064f02a7936470d12a50802a9c98a4.svg "Repobeats analytics image")
Expand All @@ -120,3 +120,10 @@ Please read the [RELEASE.MD](./RELEASE.MD)
[Get a box of waffles and some of the best app monitoring from Appsignal](https://appsignal.com/r/93dbe69bfb) 🧇

[Get $100 in credits from Digital Ocean](https://www.digitalocean.com/?refcode=efc1fe881d74&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge) 💸

## Other Open Source Work

- [`active_storage-blurhash`](https://github.com/avo-hq/active_storage-blurhash) - A plug-n-play [blurhash](https://blurha.sh/) integration for images stored in ActiveStorage
- [`class_variants`](https://github.com/avo-hq/class_variants) - Easily configure styles and apply them as classes. Very useful when you're implementing Tailwind CSS components and call them with different states.
- [`prop_initializer`](https://github.com/avo-hq/prop_initializer) - A flexible tool for defining properties on Ruby classes.
- [`stimulus-confetti`](https://github.com/avo-hq/stimulus-confetti) - The easiest way to add confetti to your StimulusJS app
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<%= @field.value_label %>
</div>
<% if @view.form? %>
<div class="flex items-center justify-center p-2 px-3 border-l border-gray-600">
<div class="flex items-center justify-center p-2 px-[48px] border-l border-gray-600">
<a href="javascript:void(0);"
title="<%= @field.action_text %>"
data-tippy="tooltip"
Expand Down
87 changes: 74 additions & 13 deletions app/javascript/js/controllers/fields/key_value_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import * as DOMPurify from 'dompurify'
import { Controller } from '@hotwired/stimulus'
import { castBoolean } from '../../helpers/cast_boolean'
import Sortable from 'sortablejs'

export default class extends Controller {
static targets = ['input', 'controller', 'rows']
Expand Down Expand Up @@ -46,6 +47,27 @@ export default class extends Controller {
this.updateKeyValueComponent()
}

moveKey(fromIndex, toIndex) {
if (!this.options.editable) return

this.fieldValue = this.moveElement(this.fieldValue, fromIndex, toIndex)

this.updateTextareaInput()
this.updateKeyValueComponent()
}

moveElement(array, fromIndex, toIndex) {
const element = array[fromIndex]

// remove 1 item at fromIndex
array.splice(fromIndex, 1)

// insert element at toIndex
array.splice(toIndex, 0, element)

return array
}

focusLastRow() {
return this.rowsTarget.querySelector('.flex.key-value-row:last-child .key-value-input-key').focus()
}
Expand Down Expand Up @@ -85,26 +107,35 @@ export default class extends Controller {
index++
})
this.rowsTarget.innerHTML = result
this.#initDragNDrop()
window.initTippy()
}

#initDragNDrop() {
const vm = this
// eslint-disable-next-line no-new
new Sortable(this.rowsTarget, {
animation: 150,
handle: '[data-control="dnd-handle"]',
onUpdate(event) {
vm.moveKey(event.oldIndex, event.newIndex)
},
})
}

interpolatedRow(key, value, index) {
let result = `<div class="flex key-value-row">
let result = '<div class="flex key-value-row">'

result += `
${this.inputField('key', index, key, value)}
${this.inputField('value', index, key, value)}`
${this.inputField('value', index, key, value)}
`

if (this.options.editable) {
result += `<a
href="javascript:void(0);"
data-key-value-index-param="${index}"
data-action="click->key-value#deleteRow"
title="${this.options.delete_text}"
data-tippy="tooltip"
data-button="delete-row"
tabindex="-1"
${this.options.disable_deleting_rows ? "disabled='disabled'" : ''}
class="flex items-center justify-center p-2 px-3 border-none ${this.options.disable_deleting_rows ? 'cursor-not-allowed' : ''}"
><svg class="pointer-events-none text-gray-500 h-5 hover:text-gray-500" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg></a>`
result += this.dndIcon(index)
result += this.deleteButton(index)
}

result += '</div>'

return result
Expand All @@ -123,6 +154,36 @@ export default class extends Controller {
/>`
}

deleteButton(index) {
return `<a
href="javascript:void(0);"
data-key-value-index-param="${index}"
data-action="click->key-value#deleteRow"
title="${this.options.delete_text}"
data-tippy="tooltip"
data-button="delete-row"
tabindex="-1"
${this.options.disable_deleting_rows ? "disabled='disabled'" : ''}
class="flex items-center justify-center p-2 px-3 border-none ${this.options.disable_deleting_rows ? 'cursor-not-allowed' : ''}"
><svg class="pointer-events-none text-gray-500 h-5 hover:text-gray-500" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" stroke="currentColor"><path d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg></a>`
}

dndIcon(index) {
return `<div class="flex flex-col">
<a
href="javascript:void(0);"
data-key-value-index-param="${index}"
data-control="dnd-handle"
title="${this.options.reorder_text}"
data-tippy="tooltip"
tabindex="-1"
class="flex items-center justify-center py-0 px-2 border-none h-full ${this.options.disable_deleting_rows ? 'cursor-not-allowed' : ''}"
>
<svg class="pointer-events-none text-gray-500 h-4 hover:text-gray-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" /></svg>
</a>
</div>`
}

setOptions() {
let fieldOptions

Expand Down
46 changes: 13 additions & 33 deletions lib/avo/fields/key_value_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,18 @@ module Fields
class KeyValueField < BaseField
attr_reader :disable_editing_keys
attr_reader :disable_adding_rows
attr_reader :key_label, :value_label, :action_text

def initialize(id, **args, &block)
super(id, **args, &block)

hide_on :index

@key_label = args[:key_label] if args[:key_label].present?
@value_label = args[:value_label] if args[:value_label].present?
@action_text = args[:action_text] if args[:action_text].present? # This should be add_row_label
@delete_text = args[:delete_text] if args[:delete_text].present?
@key_label = args[:key_label] || I18n.translate("avo.key_value_field.key")
@value_label = args[:value_label] || I18n.translate("avo.key_value_field.value")
@action_text = args[:action_text] || I18n.translate("avo.key_value_field.add_row")
@delete_text = args[:delete_text] || I18n.translate("avo.key_value_field.delete_row")
@reorder_text = args[:reorder_text] || I18n.translate("avo.key_value_field.reorder_row")

if args[:disabled] == true
@disable_editing_keys = true
Expand All @@ -36,30 +38,6 @@ def initialize(id, **args, &block)
end
end

def key_label
return @key_label if @key_label.present?

I18n.translate("avo.key_value_field.key")
end

def value_label
return @value_label if @value_label.present?

I18n.translate("avo.key_value_field.value")
end

def action_text
return @action_text if @action_text.present?

I18n.translate("avo.key_value_field.add_row")
end

def delete_text
return @delete_text if @delete_text.present?

I18n.translate("avo.key_value_field.delete_row")
end

def to_permitted_param
[:"#{id}", "#{id}": {}]
end
Expand All @@ -72,24 +50,26 @@ def parsed_value

def options
{
key_label: key_label,
value_label: value_label,
action_text: action_text,
delete_text: delete_text,
key_label: @key_label,
value_label: @value_label,
action_text: @action_text,
delete_text: @delete_text,
reorder_text: @reorder_text,
disable_editing_keys: @disable_editing_keys,
disable_editing_values: @disable_editing_values,
disable_adding_rows: @disable_adding_rows,
disable_deleting_rows: @disable_deleting_rows
}
end

def fill_field(record, key, value, params)
def fill_field(record, key, value, _params)
begin
new_value = JSON.parse(value)
rescue
new_value = {}
end

record.send(:"#{key}_will_change!")
record.send(:"#{key}=", new_value)

record
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.ar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ ar:
add_row: إضافة صف
delete_row: حذف صف
key: المفتاح
reorder_row: إعادة ترتيب الصف
value: القيمة
list_is_empty: القائمة فارغة
loading: جاري التحميل
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ en:
add_row: Add row
delete_row: Delete row
key: Key
reorder_row: Reorder row
value: Value
list_is_empty: List is empty
loading: Loading
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ es:
add_row: Añadir fila
delete_row: Eliminar fila
key: Clave
reorder_row: Reordenar fila
value: Valor
list_is_empty: La lista está vacía
loading: Cargando
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ fr:
add_row: Ajouter une ligne
delete_row: Supprimer une ligne
key: Clé
reorder_row: Réorganiser la ligne
value: Valeur
list_is_empty: La liste est vide
loading: Chargement
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ ja:
add_row: 行を追加
delete_row: 行を削除
key: キー
reorder_row: 行の並べ替え
value:
list_is_empty: リストは空です
loading: 読み込み中
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.nb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ nb:
add_row: Legg til rad
delete_row: Slett rad
key: Nøkkel
reorder_row: Omorganiser rad
value: Verdi
list_is_empty: Listen er tom
loading: Laster
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.nn.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ nn:
add_row: Legg til rad
delete_row: Slett rad
key: Nøkkel
reorder_row: Omorganiser rad
value: Verdi
list_is_empty: Lista er tom
loading: Lastar
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pt-BR:
add_row: Adicionar linha
delete_row: Remover linha
key: Chave
reorder_row: Reordenar linha
value: Valor
list_is_empty: Lista vazia
loading: Carregando
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.pt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ pt:
add_row: Adicionar linha
delete_row: Apagar linha
key: Chave
reorder_row: Reordenar linha
value: Valor
list_is_empty: Lista vazia
loading: A carregar
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.ro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ ro:
add_row: Adaugă rând
delete_row: Șterge rând
key: Cheie
reorder_row: Reordonează rând
value: Valoare
list_is_empty: Lista este goală
loading: Se incarcă
Expand Down
1 change: 1 addition & 0 deletions lib/generators/avo/templates/locales/avo.tr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ tr:
add_row: Satır ekle
delete_row: Satır sil
key: Anahtar
reorder_row: Satırı yeniden sırala
value: Değer
list_is_empty: Boş liste
loading: Yükleniyor
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"postcss-nested-ancestors": "^3.0.0",
"postcss-preset-env": "^8.5.1",
"regenerator-runtime": "^0.13.11",
"sortablejs": "^1.15.3",
"stimulus-rails-nested-form": "^4.1.0",
"stimulus-textarea-autogrow": "^4.1.0",
"stimulus-use": "^0.50.0",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5349,6 +5349,11 @@ side-channel@^1.0.4:
get-intrinsic "^1.0.2"
object-inspect "^1.9.0"

sortablejs@^1.15.3:
version "1.15.3"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.15.3.tgz#033668db5ebfb11167d1249ab88e748f27959e29"
integrity sha512-zdK3/kwwAK1cJgy1rwl1YtNTbRmc8qW/+vgXf75A7NHag5of4pyI6uK86ktmQETyWRH7IGaE73uZOOBcGxgqZg==

source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
Expand Down

0 comments on commit 08269fc

Please sign in to comment.