Browse code

Improve grid selection UX

Benjamin Roth authored on03/03/2025 19:52:31
Showing1 changed files
... ...
@@ -131,7 +131,7 @@ function init() {
131 131
   /**
132 132
    * Aktualisiert die visuelle Darstellung des Grids
133 133
    */
134
-  function updateGrid(gridContainer) {
134
+  function nupdateGrid(gridContainer) {
135 135
     // Entferne die "selected"-Klasse von allen Zellen
136 136
     gridContainer.querySelectorAll('.grid-cell').forEach(cell => {
137 137
       cell.classList.remove('selected');
... ...
@@ -145,6 +145,22 @@ function init() {
145 145
     });
146 146
   }
147 147
 
148
+  function updateGrid(gridContainer) {
149
+    const cells = gridContainer.querySelectorAll('.grid-cell');
150
+    const { selectedIndices, gridCols } = gridContainer;
151
+
152
+    cells.forEach((cell, index) => {
153
+      cell.classList.remove('selected', 'selectable');
154
+
155
+      if (selectedIndices.includes(index)) {
156
+        cell.classList.add('selected'); // Markiere ausgewählte Zellen
157
+      } else {
158
+        cell.classList.add('selectable'); // Markiere verfügbare Zellen
159
+      }
160
+    });
161
+  }
162
+
163
+
148 164
   /**
149 165
    * Speichert die Auswahl in einem versteckten Eingabeelement
150 166
    */
Browse code

Allow for multiple GridPosition widgets to work

Benjamin Roth authored on28/02/2025 14:53:21
Showing1 changed files
... ...
@@ -1,95 +1,102 @@
1 1
 function init() {
2
-  const gridContainer = document.querySelector('.grid-container');
2
+  const gridContainers = document.querySelectorAll('.vr--grid-pos-container');
3 3
 
4
-  if (!gridContainer) return;
4
+  if (!gridContainers.length) return;
5 5
 
6
-  // Werte aus den data-Attributen holen oder Standardwerte setzen
7
-  const gridRows = parseInt(gridContainer.dataset.rows) || 1; // Standardwert für Reihen: 5
8
-  const gridCols = parseInt(gridContainer.dataset.cols) || 6; // Standardwert für Spalten: 5
9
-  let selectedIndices = [];
10
-
11
-  // Grid erstellen
12
-  const fragment = document.createDocumentFragment();
13
-  // Erstelle die Zellen basierend auf Reihen und Spalten (gridRows * gridCols)
14
-  for (let i = 0; i < gridRows * gridCols; i++)
6
+  for (const gridContainer of gridContainers)
15 7
   {
16
-    const gridItem = document.createElement('div');
17
-    gridItem.classList.add('grid-cell');
18
-    gridItem.dataset.index = i; // Weise jeder Zelle ihren Index zu
19
-    gridItem.addEventListener('click', () => toggleCell(i)); // Event für Klick hinzufügen
20
-    fragment.appendChild(gridItem);
8
+
9
+    // Werte aus den data-Attributen holen oder Standardwerte setzen
10
+    gridContainer.gridRows = parseInt(gridContainer.dataset.rows) || 1; // Standardwert für Reihen: 5
11
+    gridContainer.gridCols = parseInt(gridContainer.dataset.cols) || 6; // Standardwert für Spalten: 5
12
+    gridContainer.dataField = document.getElementById(gridContainer.dataset.fieldId);
13
+
14
+    gridContainer.selectedIndices = gridContainer.selectedIndices || [];
15
+
16
+    // Grid erstellen
17
+    const fragment = document.createDocumentFragment();
18
+    // Erstelle die Zellen basierend auf Reihen und Spalten (gridRows * gridCols)
19
+    for (let i = 0; i < gridContainer.gridRows * gridContainer.gridCols; i++)
20
+    {
21
+      const gridItem = document.createElement('div');
22
+      gridItem.classList.add('grid-cell');
23
+      gridItem.dataset.index = i; // Weise jeder Zelle ihren Index zu
24
+      gridItem.addEventListener('click', () => toggleCell(gridContainer,i)); // Event für Klick hinzufügen
25
+      fragment.appendChild(gridItem);
26
+    }
27
+    gridContainer.appendChild(fragment);
28
+    loadSelection(gridContainer);
21 29
   }
22
-  gridContainer.appendChild(fragment);
23 30
 
24 31
   /**
25 32
    * Zelle ein-/auswählen
26 33
    * @param {number} index - Der Index der Zelle
27 34
    */
28
-  function toggleCell(index) {
29
-    if (selectedIndices.includes(index))
35
+  function toggleCell(gridContainer,index) {
36
+    if (gridContainer.selectedIndices.includes(index))
30 37
     {
31 38
       // Zelle abwählen
32
-      removeRowOrColumn(index);
39
+      removeRowOrColumn(gridContainer,index);
33 40
     } else
34 41
     {
35 42
       // Zelle hinzufügen
36
-      selectedIndices.push(index);
37
-      fillSelection(); // Rechteck auffüllen
43
+      gridContainer.selectedIndices.push(index);
44
+      fillSelection(gridContainer); // Rechteck auffüllen
38 45
     }
39 46
 
40
-    updateGrid(); // Visuelle Aktualisierung
41
-    saveSelection(); // Speicherung der Auswahl
47
+    updateGrid(gridContainer); // Visuelle Aktualisierung
48
+    saveSelection(gridContainer); // Speicherung der Auswahl
42 49
   }
43 50
 
44 51
   /**
45 52
    * Entfernt eine Zeile oder eine Spalte basierend auf dem Index
46 53
    * @param {number} index - Der Index der Zelle
47 54
    */
48
-  function removeRowOrColumn(index) {
49
-    const row = Math.floor(index / gridCols); // Berechnet die aktuelle Reihe
50
-    const col = index % gridCols; // Berechnet die aktuelle Spalte
55
+  function removeRowOrColumn(gridContainer,index) {
56
+    const row = Math.floor(index / gridContainer.gridCols); // Berechnet die aktuelle Reihe
57
+    const col = index % gridContainer.gridCols; // Berechnet die aktuelle Spalte
51 58
 
52 59
     if (col === 0)
53 60
     {
54 61
       // Entferne die gesamte Reihe, wenn das Feld in der ersten Spalte ist
55 62
       // UND entferne alle darunterliegenden Reihen
56
-      selectedIndices = selectedIndices.filter(cell => {
57
-        const cellRow = Math.floor(cell / gridCols);
63
+      gridContainer.selectedIndices = gridContainer.selectedIndices.filter(cell => {
64
+        const cellRow = Math.floor(cell / gridContainer.gridCols);
58 65
         return cellRow < row; // Entferne alle Reihen >= der aktuellen
59 66
       });
60 67
     } else
61 68
     {
62 69
       // Entferne die gesamte Spalte, wenn das Feld NICHT in der ersten Spalte ist
63
-      selectedIndices = selectedIndices.filter(cell => {
64
-        const cellCol = cell % gridCols;
70
+      gridContainer.selectedIndices = gridContainer.selectedIndices.filter(cell => {
71
+        const cellCol = cell % gridContainer.gridCols;
65 72
         return cellCol !== col;
66 73
       });
67 74
 
68 75
       // Prüfe, ob Spalten rechts entfernt werden müssen, um Lücken zu vermeiden
69
-      adjustRectangleAfterColumnRemoval();
76
+      adjustRectangleAfterColumnRemoval(gridContainer);
70 77
     }
71 78
   }
72 79
 
73 80
   /**
74 81
    * Passt das Rechteck an, indem leere Spalten rechts entfernt werden
75 82
    */
76
-  function adjustRectangleAfterColumnRemoval() {
77
-    if (selectedIndices.length === 0) return;
83
+  function adjustRectangleAfterColumnRemoval(gridContainer) {
84
+    if (gridContainer.selectedIndices.length === 0) return;
78 85
 
79 86
     // Ermittle die minimalen und maximalen Spalten des bestehenden Rechtecks
80
-    const firstCol = Math.min(...selectedIndices.map(cell => cell % gridCols));
81
-    const lastCol = Math.max(...selectedIndices.map(cell => cell % gridCols));
87
+    const firstCol = Math.min(...gridContainer.selectedIndices.map(cell => cell % gridContainer.gridCols));
88
+    const lastCol = Math.max(...gridContainer.selectedIndices.map(cell => cell % gridContainer.gridCols));
82 89
 
83 90
     // Prüfe jede Spalte von links nach rechts
84 91
     for (let col = firstCol; col <= lastCol; col++)
85 92
     {
86 93
       // Überprüfen, ob diese Spalte komplett leer ist
87
-      const isColumnEmpty = !selectedIndices.some(cell => cell % gridCols === col);
94
+      const isColumnEmpty = !gridContainer.selectedIndices.some(cell => cell % gridContainer.gridCols === col);
88 95
 
89 96
       if (isColumnEmpty)
90 97
       {
91 98
         // Entferne alle Zellen in Spalten rechts von der leeren Spalte
92
-        selectedIndices = selectedIndices.filter(cell => cell % gridCols < col);
99
+        gridContainer.selectedIndices = gridContainer.selectedIndices.filter(cell => cell % gridContainer.gridCols < col);
93 100
         break; // Stoppe, wenn wir eine leere Spalte finden
94 101
       }
95 102
     }
... ...
@@ -98,14 +105,14 @@ function init() {
98 105
   /**
99 106
    * Füllt die Auswahl als Rechteck aus
100 107
    */
101
-  function fillSelection() {
102
-    if (selectedIndices.length === 0) return;
108
+  function fillSelection(gridContainer) {
109
+    if (gridContainer.selectedIndices.length === 0) return;
103 110
 
104 111
     // Ermittle die minimalen und maximalen Indizes
105
-    const firstRow = Math.min(...selectedIndices.map(cell => Math.floor(cell / gridCols)));
106
-    const lastRow = Math.max(...selectedIndices.map(cell => Math.floor(cell / gridCols)));
107
-    const firstCol = Math.min(...selectedIndices.map(cell => cell % gridCols));
108
-    const lastCol = Math.max(...selectedIndices.map(cell => cell % gridCols));
112
+    const firstRow = Math.min(...gridContainer.selectedIndices.map(cell => Math.floor(cell / gridContainer.gridCols)));
113
+    const lastRow = Math.max(...gridContainer.selectedIndices.map(cell => Math.floor(cell / gridContainer.gridCols)));
114
+    const firstCol = Math.min(...gridContainer.selectedIndices.map(cell => cell % gridContainer.gridCols));
115
+    const lastCol = Math.max(...gridContainer.selectedIndices.map(cell => cell % gridContainer.gridCols));
109 116
 
110 117
     // Rechteck auffüllen: Alle Zellen zwischen den Ecken hinzufügen
111 118
     const newSelection = [];
... ...
@@ -113,25 +120,25 @@ function init() {
113 120
     {
114 121
       for (let col = firstCol; col <= lastCol; col++)
115 122
       {
116
-        const cellIndex = row * gridCols + col;
123
+        const cellIndex = row * gridContainer.gridCols + col;
117 124
         newSelection.push(cellIndex);
118 125
       }
119 126
     }
120 127
 
121
-    selectedIndices = newSelection; // Aktualisiere die Auswahl
128
+    gridContainer.selectedIndices = newSelection; // Aktualisiere die Auswahl
122 129
   }
123 130
 
124 131
   /**
125 132
    * Aktualisiert die visuelle Darstellung des Grids
126 133
    */
127
-  function updateGrid() {
134
+  function updateGrid(gridContainer) {
128 135
     // Entferne die "selected"-Klasse von allen Zellen
129 136
     gridContainer.querySelectorAll('.grid-cell').forEach(cell => {
130 137
       cell.classList.remove('selected');
131 138
     });
132 139
 
133 140
     // Füge die "selected"-Klasse zu den ausgewählten Zellen hinzu
134
-    selectedIndices.forEach(index => {
141
+    gridContainer.selectedIndices.forEach(index => {
135 142
       gridContainer
136 143
         .querySelector(`.grid-cell[data-index="${index}"]`)
137 144
         ?.classList.add('selected');
... ...
@@ -141,11 +148,23 @@ function init() {
141 148
   /**
142 149
    * Speichert die Auswahl in einem versteckten Eingabeelement
143 150
    */
144
-  function saveSelection() {
145
-    const gridDataInput = document.getElementById('ctrl_vr_gpw_grid');
151
+  function saveSelection(gridContainer) {
152
+    const gridDataInput = gridContainer.dataField;
153
+    if (gridDataInput)
154
+    {
155
+      gridDataInput.value = JSON.stringify(gridContainer.selectedIndices);
156
+    }
157
+  }
158
+
159
+  /**
160
+   * Lädt die Auswahl aus einem versteckten Eingabeelement
161
+   */
162
+  function loadSelection(gridContainer) {
163
+    const gridDataInput = gridContainer.dataField;
146 164
     if (gridDataInput)
147 165
     {
148
-      gridDataInput.value = JSON.stringify(selectedIndices);
166
+      gridContainer.selectedIndices = JSON.parse(gridDataInput.value);
167
+      updateGrid(gridContainer);
149 168
     }
150 169
   }
151 170
 }
Browse code

Add turbo frame compatibility

Benjamin Roth authored on28/02/2025 13:41:43
Showing1 changed files
... ...
@@ -1,4 +1,4 @@
1
-document.addEventListener('DOMContentLoaded', () => {
1
+function init() {
2 2
   const gridContainer = document.querySelector('.grid-container');
3 3
 
4 4
   if (!gridContainer) return;
... ...
@@ -11,7 +11,8 @@ document.addEventListener('DOMContentLoaded', () => {
11 11
   // Grid erstellen
12 12
   const fragment = document.createDocumentFragment();
13 13
   // Erstelle die Zellen basierend auf Reihen und Spalten (gridRows * gridCols)
14
-  for (let i = 0; i < gridRows * gridCols; i++) {
14
+  for (let i = 0; i < gridRows * gridCols; i++)
15
+  {
15 16
     const gridItem = document.createElement('div');
16 17
     gridItem.classList.add('grid-cell');
17 18
     gridItem.dataset.index = i; // Weise jeder Zelle ihren Index zu
... ...
@@ -25,10 +26,12 @@ document.addEventListener('DOMContentLoaded', () => {
25 26
    * @param {number} index - Der Index der Zelle
26 27
    */
27 28
   function toggleCell(index) {
28
-    if (selectedIndices.includes(index)) {
29
+    if (selectedIndices.includes(index))
30
+    {
29 31
       // Zelle abwählen
30 32
       removeRowOrColumn(index);
31
-    } else {
33
+    } else
34
+    {
32 35
       // Zelle hinzufügen
33 36
       selectedIndices.push(index);
34 37
       fillSelection(); // Rechteck auffüllen
... ...
@@ -46,14 +49,16 @@ document.addEventListener('DOMContentLoaded', () => {
46 49
     const row = Math.floor(index / gridCols); // Berechnet die aktuelle Reihe
47 50
     const col = index % gridCols; // Berechnet die aktuelle Spalte
48 51
 
49
-    if (col === 0) {
52
+    if (col === 0)
53
+    {
50 54
       // Entferne die gesamte Reihe, wenn das Feld in der ersten Spalte ist
51 55
       // UND entferne alle darunterliegenden Reihen
52 56
       selectedIndices = selectedIndices.filter(cell => {
53 57
         const cellRow = Math.floor(cell / gridCols);
54 58
         return cellRow < row; // Entferne alle Reihen >= der aktuellen
55 59
       });
56
-    } else {
60
+    } else
61
+    {
57 62
       // Entferne die gesamte Spalte, wenn das Feld NICHT in der ersten Spalte ist
58 63
       selectedIndices = selectedIndices.filter(cell => {
59 64
         const cellCol = cell % gridCols;
... ...
@@ -76,11 +81,13 @@ document.addEventListener('DOMContentLoaded', () => {
76 81
     const lastCol = Math.max(...selectedIndices.map(cell => cell % gridCols));
77 82
 
78 83
     // Prüfe jede Spalte von links nach rechts
79
-    for (let col = firstCol; col <= lastCol; col++) {
84
+    for (let col = firstCol; col <= lastCol; col++)
85
+    {
80 86
       // Überprüfen, ob diese Spalte komplett leer ist
81 87
       const isColumnEmpty = !selectedIndices.some(cell => cell % gridCols === col);
82 88
 
83
-      if (isColumnEmpty) {
89
+      if (isColumnEmpty)
90
+      {
84 91
         // Entferne alle Zellen in Spalten rechts von der leeren Spalte
85 92
         selectedIndices = selectedIndices.filter(cell => cell % gridCols < col);
86 93
         break; // Stoppe, wenn wir eine leere Spalte finden
... ...
@@ -102,8 +109,10 @@ document.addEventListener('DOMContentLoaded', () => {
102 109
 
103 110
     // Rechteck auffüllen: Alle Zellen zwischen den Ecken hinzufügen
104 111
     const newSelection = [];
105
-    for (let row = firstRow; row <= lastRow; row++) {
106
-      for (let col = firstCol; col <= lastCol; col++) {
112
+    for (let row = firstRow; row <= lastRow; row++)
113
+    {
114
+      for (let col = firstCol; col <= lastCol; col++)
115
+      {
107 116
         const cellIndex = row * gridCols + col;
108 117
         newSelection.push(cellIndex);
109 118
       }
... ...
@@ -133,9 +142,12 @@ document.addEventListener('DOMContentLoaded', () => {
133 142
    * Speichert die Auswahl in einem versteckten Eingabeelement
134 143
    */
135 144
   function saveSelection() {
136
-    const gridDataInput = document.getElementById('grid-data');
137
-    if (gridDataInput) {
145
+    const gridDataInput = document.getElementById('ctrl_vr_gpw_grid');
146
+    if (gridDataInput)
147
+    {
138 148
       gridDataInput.value = JSON.stringify(selectedIndices);
139 149
     }
140 150
   }
141
-});
151
+}
152
+document.addEventListener('DOMContentLoaded', () => {init()});
153
+document.addEventListener('turbo:render', () => {init()});
Browse code

Add new GridPosition widget

Benjamin Roth authored on28/02/2025 10:55:44
Showing1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,141 @@
1
+document.addEventListener('DOMContentLoaded', () => {
2
+  const gridContainer = document.querySelector('.grid-container');
3
+
4
+  if (!gridContainer) return;
5
+
6
+  // Werte aus den data-Attributen holen oder Standardwerte setzen
7
+  const gridRows = parseInt(gridContainer.dataset.rows) || 1; // Standardwert für Reihen: 5
8
+  const gridCols = parseInt(gridContainer.dataset.cols) || 6; // Standardwert für Spalten: 5
9
+  let selectedIndices = [];
10
+
11
+  // Grid erstellen
12
+  const fragment = document.createDocumentFragment();
13
+  // Erstelle die Zellen basierend auf Reihen und Spalten (gridRows * gridCols)
14
+  for (let i = 0; i < gridRows * gridCols; i++) {
15
+    const gridItem = document.createElement('div');
16
+    gridItem.classList.add('grid-cell');
17
+    gridItem.dataset.index = i; // Weise jeder Zelle ihren Index zu
18
+    gridItem.addEventListener('click', () => toggleCell(i)); // Event für Klick hinzufügen
19
+    fragment.appendChild(gridItem);
20
+  }
21
+  gridContainer.appendChild(fragment);
22
+
23
+  /**
24
+   * Zelle ein-/auswählen
25
+   * @param {number} index - Der Index der Zelle
26
+   */
27
+  function toggleCell(index) {
28
+    if (selectedIndices.includes(index)) {
29
+      // Zelle abwählen
30
+      removeRowOrColumn(index);
31
+    } else {
32
+      // Zelle hinzufügen
33
+      selectedIndices.push(index);
34
+      fillSelection(); // Rechteck auffüllen
35
+    }
36
+
37
+    updateGrid(); // Visuelle Aktualisierung
38
+    saveSelection(); // Speicherung der Auswahl
39
+  }
40
+
41
+  /**
42
+   * Entfernt eine Zeile oder eine Spalte basierend auf dem Index
43
+   * @param {number} index - Der Index der Zelle
44
+   */
45
+  function removeRowOrColumn(index) {
46
+    const row = Math.floor(index / gridCols); // Berechnet die aktuelle Reihe
47
+    const col = index % gridCols; // Berechnet die aktuelle Spalte
48
+
49
+    if (col === 0) {
50
+      // Entferne die gesamte Reihe, wenn das Feld in der ersten Spalte ist
51
+      // UND entferne alle darunterliegenden Reihen
52
+      selectedIndices = selectedIndices.filter(cell => {
53
+        const cellRow = Math.floor(cell / gridCols);
54
+        return cellRow < row; // Entferne alle Reihen >= der aktuellen
55
+      });
56
+    } else {
57
+      // Entferne die gesamte Spalte, wenn das Feld NICHT in der ersten Spalte ist
58
+      selectedIndices = selectedIndices.filter(cell => {
59
+        const cellCol = cell % gridCols;
60
+        return cellCol !== col;
61
+      });
62
+
63
+      // Prüfe, ob Spalten rechts entfernt werden müssen, um Lücken zu vermeiden
64
+      adjustRectangleAfterColumnRemoval();
65
+    }
66
+  }
67
+
68
+  /**
69
+   * Passt das Rechteck an, indem leere Spalten rechts entfernt werden
70
+   */
71
+  function adjustRectangleAfterColumnRemoval() {
72
+    if (selectedIndices.length === 0) return;
73
+
74
+    // Ermittle die minimalen und maximalen Spalten des bestehenden Rechtecks
75
+    const firstCol = Math.min(...selectedIndices.map(cell => cell % gridCols));
76
+    const lastCol = Math.max(...selectedIndices.map(cell => cell % gridCols));
77
+
78
+    // Prüfe jede Spalte von links nach rechts
79
+    for (let col = firstCol; col <= lastCol; col++) {
80
+      // Überprüfen, ob diese Spalte komplett leer ist
81
+      const isColumnEmpty = !selectedIndices.some(cell => cell % gridCols === col);
82
+
83
+      if (isColumnEmpty) {
84
+        // Entferne alle Zellen in Spalten rechts von der leeren Spalte
85
+        selectedIndices = selectedIndices.filter(cell => cell % gridCols < col);
86
+        break; // Stoppe, wenn wir eine leere Spalte finden
87
+      }
88
+    }
89
+  }
90
+
91
+  /**
92
+   * Füllt die Auswahl als Rechteck aus
93
+   */
94
+  function fillSelection() {
95
+    if (selectedIndices.length === 0) return;
96
+
97
+    // Ermittle die minimalen und maximalen Indizes
98
+    const firstRow = Math.min(...selectedIndices.map(cell => Math.floor(cell / gridCols)));
99
+    const lastRow = Math.max(...selectedIndices.map(cell => Math.floor(cell / gridCols)));
100
+    const firstCol = Math.min(...selectedIndices.map(cell => cell % gridCols));
101
+    const lastCol = Math.max(...selectedIndices.map(cell => cell % gridCols));
102
+
103
+    // Rechteck auffüllen: Alle Zellen zwischen den Ecken hinzufügen
104
+    const newSelection = [];
105
+    for (let row = firstRow; row <= lastRow; row++) {
106
+      for (let col = firstCol; col <= lastCol; col++) {
107
+        const cellIndex = row * gridCols + col;
108
+        newSelection.push(cellIndex);
109
+      }
110
+    }
111
+
112
+    selectedIndices = newSelection; // Aktualisiere die Auswahl
113
+  }
114
+
115
+  /**
116
+   * Aktualisiert die visuelle Darstellung des Grids
117
+   */
118
+  function updateGrid() {
119
+    // Entferne die "selected"-Klasse von allen Zellen
120
+    gridContainer.querySelectorAll('.grid-cell').forEach(cell => {
121
+      cell.classList.remove('selected');
122
+    });
123
+
124
+    // Füge die "selected"-Klasse zu den ausgewählten Zellen hinzu
125
+    selectedIndices.forEach(index => {
126
+      gridContainer
127
+        .querySelector(`.grid-cell[data-index="${index}"]`)
128
+        ?.classList.add('selected');
129
+    });
130
+  }
131
+
132
+  /**
133
+   * Speichert die Auswahl in einem versteckten Eingabeelement
134
+   */
135
+  function saveSelection() {
136
+    const gridDataInput = document.getElementById('grid-data');
137
+    if (gridDataInput) {
138
+      gridDataInput.value = JSON.stringify(selectedIndices);
139
+    }
140
+  }
141
+});