6363 margin-top : 1.25rem ;
6464 }
6565
66+ .column-filter-card {
67+ display : grid;
68+ gap : 1rem ;
69+ margin-bottom : 1.25rem ;
70+ padding : 1rem ;
71+ border : 1px solid var (--border-subtle , # d0d5dd );
72+ border-radius : 0.75rem ;
73+ background : var (--background-subtle , # f8f9fb );
74+ }
75+
76+ .column-filter-header {
77+ display : flex;
78+ flex-wrap : wrap;
79+ gap : 0.75rem ;
80+ justify-content : space-between;
81+ align-items : center;
82+ }
83+
84+ .column-checkboxes {
85+ display : grid;
86+ gap : 0.5rem ;
87+ grid-template-columns : repeat (auto-fill, minmax (180px , 1fr ));
88+ }
89+
90+ .checkbox-row {
91+ display : inline-flex;
92+ align-items : center;
93+ gap : 0.5rem ;
94+ }
95+
6696 .jump-group {
6797 display : inline-flex;
6898 align-items : center;
@@ -102,6 +132,17 @@ <h1>CSV Row Viewer</h1>
102132 < span id ="file-summary "> </ span >
103133 </ div >
104134
135+ < div id ="column-filter-card " class ="column-filter-card " hidden >
136+ < div class ="column-filter-header ">
137+ < strong > Visible columns</ strong >
138+ < label class ="checkbox-row " for ="toggle-all-columns ">
139+ < input id ="toggle-all-columns " type ="checkbox ">
140+ < span > Select all</ span >
141+ </ label >
142+ </ div >
143+ < div id ="column-checkboxes " class ="column-checkboxes "> </ div >
144+ </ div >
145+
105146 < div class ="table-container " aria-live ="polite ">
106147 < table >
107148 < thead >
@@ -142,10 +183,15 @@ <h1>CSV Row Viewer</h1>
142183 const nextButton = document . getElementById ( 'next-row' ) ;
143184 const jumpInput = document . getElementById ( 'jump-row' ) ;
144185 const jumpButton = document . getElementById ( 'jump-button' ) ;
186+ const columnFilterCard = document . getElementById ( 'column-filter-card' ) ;
187+ const columnCheckboxes = document . getElementById ( 'column-checkboxes' ) ;
188+ const toggleAllColumns = document . getElementById ( 'toggle-all-columns' ) ;
145189
146190 let headers = [ ] ;
147191 let rows = [ ] ;
148192 let currentIndex = 0 ;
193+ let columnLabels = [ ] ;
194+ let selectedColumns = [ ] ;
149195
150196 const parseCsv = ( text ) => {
151197 const output = [ ] ;
@@ -215,26 +261,47 @@ <h1>CSV Row Viewer</h1>
215261
216262 const renderRow = ( ) => {
217263 const row = rows [ currentIndex ] || [ ] ;
218- const totalColumns = Math . max ( headers . length , row . length ) ;
219-
220- const mergedHeaders = Array . from ( { length : totalColumns } , ( _ , index ) => {
221- if ( headers [ index ] ) {
222- return headers [ index ] ;
264+ const visibleColumns = columnLabels . reduce ( ( output , header , index ) => {
265+ if ( selectedColumns [ index ] ) {
266+ output . push ( { header, index } ) ;
223267 }
224- return `Extra column ${ index - headers . length + 1 } ` ;
225- } ) ;
268+ return output ;
269+ } , [ ] ) ;
226270
227- tableBody . innerHTML = mergedHeaders . map ( ( header , index ) => {
271+ tableBody . innerHTML = visibleColumns . map ( ( { header, index } ) => {
228272 const value = row [ index ] ?? '—' ;
229273 return `\n<tr><th scope="row">${ escapeHtml ( header || `Column ${ index + 1 } ` ) } </th><td>${ escapeHtml ( value ) } </td></tr>` ;
230274 } ) . join ( '' ) ;
231275
276+ if ( visibleColumns . length === 0 ) {
277+ tableBody . innerHTML = '<tr><td colspan="2">No columns selected.</td></tr>' ;
278+ }
279+
232280 rowIndicator . textContent = `Row ${ currentIndex + 1 } of ${ rows . length } ` ;
233281 prevButton . disabled = currentIndex === 0 ;
234282 nextButton . disabled = currentIndex >= rows . length - 1 ;
235283 jumpInput . value = String ( currentIndex + 1 ) ;
236284 } ;
237285
286+ const renderColumnFilters = ( ) => {
287+ if ( ! columnLabels . length ) {
288+ columnFilterCard . hidden = true ;
289+ return ;
290+ }
291+
292+ columnFilterCard . hidden = false ;
293+ columnCheckboxes . innerHTML = columnLabels . map ( ( label , index ) => `
294+ <label class="checkbox-row" for="column-toggle-${ index } ">
295+ <input id="column-toggle-${ index } " type="checkbox" data-column-index="${ index } " ${ selectedColumns [ index ] ? 'checked' : '' } >
296+ <span>${ escapeHtml ( label || `Column ${ index + 1 } ` ) } </span>
297+ </label>
298+ ` ) . join ( '' ) ;
299+
300+ const allSelected = selectedColumns . every ( Boolean ) ;
301+ toggleAllColumns . checked = allSelected ;
302+ toggleAllColumns . indeterminate = ! allSelected && selectedColumns . some ( Boolean ) ;
303+ } ;
304+
238305 const escapeHtml = ( value ) => value
239306 . replaceAll ( '&' , '&' )
240307 . replaceAll ( '<' , '<' )
@@ -254,6 +321,14 @@ <h1>CSV Row Viewer</h1>
254321 headers = parsed [ 0 ] ;
255322 rows = parsed . slice ( 1 ) ;
256323 currentIndex = 0 ;
324+ const longestRowLength = rows . reduce ( ( max , row ) => Math . max ( max , row . length ) , headers . length ) ;
325+ columnLabels = Array . from ( { length : longestRowLength } , ( _ , index ) => {
326+ if ( headers [ index ] ) {
327+ return headers [ index ] ;
328+ }
329+ return `Extra column ${ index - headers . length + 1 } ` ;
330+ } ) ;
331+ selectedColumns = columnLabels . map ( ( ) => true ) ;
257332
258333 viewer . hidden = false ;
259334 fileSummary . textContent = `${ file . name } • ${ rows . length } data row${ rows . length === 1 ? '' : 's' } ` ;
@@ -263,11 +338,14 @@ <h1>CSV Row Viewer</h1>
263338 jumpInput . min = '1' ;
264339 jumpInput . max = String ( rows . length ) ;
265340
341+ renderColumnFilters ( ) ;
266342 renderRow ( ) ;
267343 } catch ( error ) {
268344 viewer . hidden = true ;
269345 headers = [ ] ;
270346 rows = [ ] ;
347+ columnLabels = [ ] ;
348+ selectedColumns = [ ] ;
271349 loadStatus . textContent = error . message || 'Unable to parse this CSV file.' ;
272350 loadStatus . classList . add ( 'error' ) ;
273351 }
@@ -316,6 +394,29 @@ <h1>CSV Row Viewer</h1>
316394 jumpToRow ( ) ;
317395 }
318396 } ) ;
397+
398+ columnCheckboxes . addEventListener ( 'change' , ( event ) => {
399+ const target = event . target ;
400+ if ( ! ( target instanceof HTMLInputElement ) ) {
401+ return ;
402+ }
403+
404+ const index = Number . parseInt ( target . dataset . columnIndex || '' , 10 ) ;
405+ if ( Number . isNaN ( index ) ) {
406+ return ;
407+ }
408+
409+ selectedColumns [ index ] = target . checked ;
410+ renderColumnFilters ( ) ;
411+ renderRow ( ) ;
412+ } ) ;
413+
414+ toggleAllColumns . addEventListener ( 'change' , ( ) => {
415+ const isChecked = toggleAllColumns . checked ;
416+ selectedColumns = selectedColumns . map ( ( ) => isChecked ) ;
417+ renderColumnFilters ( ) ;
418+ renderRow ( ) ;
419+ } ) ;
319420 } ) ( ) ;
320421 </ script >
321422</ body >
0 commit comments