11/* !include https://cdn.jsdelivr.net/npm/@tabler/core@1.4.0/dist/js/tabler.min.js */
2- const nonce = document . currentScript . nonce ;
2+ const nonce = /** @type { HTMLScriptElement } */ ( document . currentScript ) . nonce ;
33
44function sqlpage_card ( ) {
5- for ( const c of document . querySelectorAll ( "[data-pre-init=card]" ) ) {
5+ /** @type {NodeListOf<HTMLElement> } */
6+ const cards = document . querySelectorAll ( "[data-pre-init=card]" ) ;
7+ for ( const c of cards ) {
68 c . removeAttribute ( "data-pre-init" ) ;
9+ if ( ! c . dataset . embed ) continue ;
710 const url = new URL ( c . dataset . embed , window . location . href ) ;
811 url . searchParams . set ( "_sqlpage_embed" , "1" ) ;
912 fetch ( url )
1013 . then ( ( res ) => res . text ( ) )
1114 . then ( ( html ) => {
1215 const body = c . querySelector ( ".card-content" ) ;
13- body . innerHTML = html ;
16+ if ( body ) body . innerHTML = html ;
1417 const spinner = c . querySelector ( ".card-loading-placeholder" ) ;
15- if ( spinner ) {
16- spinner . parentNode . removeChild ( spinner ) ;
17- }
18+ spinner ?. remove ( ) ;
1819 const fragLoadedEvt = new CustomEvent ( "fragment-loaded" , {
1920 bubbles : true ,
2021 } ) ;
@@ -28,14 +29,18 @@ function setup_table(root_el) {
2829 /** @type {HTMLInputElement | null } */
2930 const search_input = root_el . querySelector ( "input.search" ) ;
3031 const table_el = root_el . querySelector ( "table" ) ;
31- const sort_buttons = [ ...table_el . querySelectorAll ( "button.sort[data-sort]" ) ] ;
32+ if ( ! table_el ) return ;
33+ /** @type {NodeListOf<HTMLElement> } */
34+ const sort_button_els = table_el . querySelectorAll ( "button.sort[data-sort]" ) ;
35+ const sort_buttons = [ ...sort_button_els ] ;
3236 const item_parent = table_el . querySelector ( "tbody" ) ;
3337 const has_sort = sort_buttons . length > 0 ;
3438
3539 if ( search_input || has_sort ) {
3640 const items = table_parse_data ( table_el , sort_buttons ) ;
3741 if ( search_input ) setup_table_search_behavior ( search_input , items ) ;
38- if ( has_sort ) setup_sort_behavior ( sort_buttons , items , item_parent ) ;
42+ if ( has_sort && item_parent )
43+ setup_sort_behavior ( sort_buttons , items , item_parent ) ;
3944 }
4045
4146 // Change number format AFTER parsing and storing the sort keys
@@ -44,7 +49,7 @@ function setup_table(root_el) {
4449
4550/**
4651 * @param {HTMLInputElement } search_input
47- * @param {Array<{el: HTMLElement, sort_keys: Array<{num: number, str: string}>}> } items
52+ * @param {TableRow[] } items
4853 */
4954function setup_table_search_behavior ( search_input , items ) {
5055 function onSearch ( ) {
@@ -66,12 +71,15 @@ function setup_table_search_behavior(search_input, items) {
6671
6772/**@param {HTMLElement } table_el */
6873function apply_number_formatting ( table_el ) {
74+ /** @type {NodeListOf<HTMLElement> } */
6975 const header_els = table_el . querySelectorAll ( "thead > tr > th" ) ;
7076 const col_types = [ ...header_els ] . map ( ( el ) => el . dataset . column_type ) ;
7177 const col_rawnums = [ ...header_els ] . map ( ( el ) => ! ! el . dataset . raw_number ) ;
7278 const col_money = [ ...header_els ] . map ( ( el ) => ! ! el . dataset . money ) ;
7379 const number_format_locale = table_el . dataset . number_format_locale ;
74- const number_format_digits = table_el . dataset . number_format_digits ;
80+ const number_format_digits = table_el . dataset . number_format_digits
81+ ? Number ( table_el . dataset . number_format_digits )
82+ : undefined ;
7583 const currency = table_el . dataset . currency ;
7684
7785 for ( const tr_el of table_el . querySelectorAll ( "tbody tr, tfoot tr" ) ) {
@@ -95,20 +103,25 @@ function apply_number_formatting(table_el) {
95103 }
96104}
97105
106+ /** @typedef { {el: HTMLElement, sort_keys: {num: number, str: string}[]} } TableRow */
107+
98108/** Prepare the table rows for sorting.
99109 * @param {HTMLElement } table_el
100110 * @param {HTMLElement[] } sort_buttons
111+ * @returns {TableRow[] }
101112 */
102113function table_parse_data ( table_el , sort_buttons ) {
103114 const is_num = [ ...sort_buttons ] . map (
104- ( btn_el ) => btn_el . parentElement . dataset . column_type === "number" ,
115+ ( btn_el ) => btn_el . parentElement ? .dataset . column_type === "number" ,
105116 ) ;
106- return [ ...table_el . querySelectorAll ( "tbody tr" ) ] . map ( ( tr_el ) => {
117+ /** @type {NodeListOf<HTMLElement> } */
118+ const row_els = table_el . querySelectorAll ( "tbody tr" ) ;
119+ return [ ...row_els ] . map ( ( tr_el ) => {
107120 const cells = tr_el . getElementsByTagName ( "td" ) ;
108121 return {
109122 el : tr_el ,
110123 sort_keys : sort_buttons . map ( ( _btn_el , idx ) => {
111- const str = cells [ idx ] ?. textContent ;
124+ const str = cells [ idx ] ?. textContent ?? "" ;
112125 const num = is_num [ idx ] ? Number . parseFloat ( str ) : Number . NaN ;
113126 return { num, str } ;
114127 } ) ,
@@ -119,7 +132,7 @@ function table_parse_data(table_el, sort_buttons) {
119132/**
120133 * Adds event listeners to the sort buttons to sort the table rows.
121134 * @param {HTMLElement[] } sort_buttons
122- * @param {HTMLElement [] } items
135+ * @param {TableRow [] } items
123136 * @param {HTMLElement } item_parent
124137 */
125138function setup_sort_behavior ( sort_buttons , items , item_parent ) {
@@ -147,7 +160,9 @@ function setup_sort_behavior(sort_buttons, items, item_parent) {
147160}
148161
149162function sqlpage_table ( ) {
150- for ( const r of document . querySelectorAll ( "[data-pre-init=table]" ) ) {
163+ /** @type {NodeListOf<HTMLElement> } */
164+ const tables = document . querySelectorAll ( "[data-pre-init=table]" ) ;
165+ for ( const r of tables ) {
151166 r . removeAttribute ( "data-pre-init" ) ;
152167 setup_table ( r ) ;
153168 }
@@ -198,13 +213,14 @@ function sqlpage_map() {
198213 }
199214 function onLeafletLoad ( ) {
200215 is_leaflet_loaded = true ;
216+ /** @type {NodeListOf<HTMLElement> } */
201217 const maps = document . querySelectorAll ( "[data-pre-init=map]" ) ;
202218 for ( const m of maps ) {
203219 const tile_source = m . dataset . tile_source ;
204- const maxZoom = + m . dataset . max_zoom ;
220+ const maxZoom = Number ( m . dataset . max_zoom ) ;
205221 const attribution = m . dataset . attribution ;
206222 const map = L . map ( m , { attributionControl : ! ! attribution } ) ;
207- const zoom = m . dataset . zoom ;
223+ const zoom = Number ( m . dataset . zoom ) ;
208224 const center = parseCoords ( m . dataset . center ) ;
209225 if ( tile_source )
210226 L . tileLayer ( tile_source , { attribution, maxZoom } ) . addTo ( map ) ;
@@ -213,14 +229,14 @@ function sqlpage_map() {
213229 setTimeout ( addMarker , 0 , marker_elem , map ) ;
214230 }
215231 setTimeout ( ( ) => {
216- if ( center ) map . setView ( center , + zoom ) ;
232+ if ( center ) map . setView ( center , zoom ) ;
217233 else {
218234 const markerBounds = ( m ) =>
219235 m . getLatLng ? m . getLatLng ( ) : m . getBounds ( ) ;
220236 const bounds = map . _sqlpage_markers . map ( markerBounds ) ;
221237 if ( bounds . length > 0 ) map . fitBounds ( bounds ) ;
222- else map . setView ( [ 51.505 , 10 ] , + zoom ) ;
223- if ( zoom != null ) map . setZoom ( + zoom ) ;
238+ else map . setView ( [ 51.505 , 10 ] , zoom ) ;
239+ if ( ! Number . isNaN ( zoom ) ) map . setZoom ( zoom ) ;
224240 }
225241 } , 100 ) ;
226242 m . removeAttribute ( "data-pre-init" ) ;
@@ -282,15 +298,16 @@ function sqlpage_map() {
282298}
283299
284300function sqlpage_form ( ) {
301+ /** @type {NodeListOf<HTMLInputElement> } */
285302 const file_inputs = document . querySelectorAll (
286303 "input[type=file][data-max-size]" ,
287304 ) ;
288305 for ( const input of file_inputs ) {
289- const max_size = + input . dataset . maxSize ;
290- input . addEventListener ( "change" , function ( ) {
306+ const max_size = Number ( input . dataset . maxSize ) ;
307+ input . addEventListener ( "change" , ( ) => {
291308 input . classList . remove ( "is-invalid" ) ;
292309 input . setCustomValidity ( "" ) ;
293- for ( const { size } of this . files ) {
310+ for ( const { size } of input . files ?? [ ] ) {
294311 if ( size > max_size ) {
295312 input . classList . add ( "is-invalid" ) ;
296313 return input . setCustomValidity (
@@ -301,6 +318,7 @@ function sqlpage_form() {
301318 } ) ;
302319 }
303320
321+ /** @type {NodeListOf<HTMLFormElement> } */
304322 const auto_submit_forms = document . querySelectorAll ( "form[data-auto-submit]" ) ;
305323 for ( const form of auto_submit_forms ) {
306324 form . addEventListener ( "change" , ( ) => form . submit ( ) ) ;
@@ -314,11 +332,13 @@ function get_tabler_color(name) {
314332}
315333
316334function load_scripts ( ) {
335+ /** @type {NodeListOf<HTMLElement> } */
317336 const addjs = document . querySelectorAll ( "[data-sqlpage-js]" ) ;
318337 const existing_scripts = new Set (
319338 [ ...document . querySelectorAll ( "script" ) ] . map ( ( s ) => s . src ) ,
320339 ) ;
321340 for ( const el of addjs ) {
341+ if ( ! el . dataset . sqlpageJs ) continue ;
322342 const js = new URL ( el . dataset . sqlpageJs , window . location . href ) . href ;
323343 if ( existing_scripts . has ( js ) ) continue ;
324344 existing_scripts . add ( js ) ;
@@ -375,11 +395,16 @@ function sqlpage_toast() {
375395 if ( ! Toast ) return ;
376396
377397 const initialized_toasts = [ ] ;
378- for ( const toast of document . querySelectorAll ( '[data-pre-init="toast"]' ) ) {
398+ /** @type {NodeListOf<HTMLElement> } */
399+ const toasts = document . querySelectorAll ( '[data-pre-init="toast"]' ) ;
400+ for ( const toast of toasts ) {
379401 const source_container = toast . parentElement ;
402+ if ( ! source_container ) continue ;
380403 const position = source_container . dataset . sqlpageToastPosition ;
381- let container = document . querySelector (
382- `.toast-container[data-sqlpage-toast-position="${ position } "]:not([data-pre-init])` ,
404+ let container = /** @type {HTMLElement | null } */ (
405+ document . querySelector (
406+ `.toast-container[data-sqlpage-toast-position="${ position } "]:not([data-pre-init])` ,
407+ )
383408 ) ;
384409 if ( ! container ) {
385410 container = source_container ;
0 commit comments