added on local at 2026-07-01 16:01:15
| 1 | class ShawnGrid extends HTMLElement { | |
| 2 | constructor() { | |
| 3 | // Always call super first in constructor | |
| 4 | super(); | |
| 5 | this.attachShadow({ mode: "open" }); | |
| 6 | console.log("Linked Version Edit"); | |
| 7 | // Setting the default global variables to be used. | |
| 8 | // Full json data from the back-end. INJECTABLE | |
| 9 | this.GLOBAL_JSON_RESULT = []; //INJECTABLE | |
| 10 | // Filtered json load from the user filters and json data. Populated by filter_json() NOT-INJECTABLE | |
| 11 | // THIS IS THE DATA THE GRID USES, IF YOU WANT TO MODIFY AND RELOAD, INJECT TO GLOBAL_JSON_RESULT | |
| 12 | this.GLOBAL_JSON_RESULT_FILTERED = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 13 | // User filters for the table pulled from the back-end. NOT-INJECTABLE | |
| 14 | this.GLOBAL_LOADED_FILTERS = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 15 | // To handle copying into the table, and undoing copy. Limited to 1 like the delete | |
| 16 | this.UNDO_COPY_JSON = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 17 | this.UNDO_OVER_COPY_JSON = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 18 | this.UNDO_INSERT_STARTING_ID = -1; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 19 | this.UNDO_INSERT_AMOUNT = -1; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 20 | this.COPY_COUNT = 0; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 21 | this.ANCHOR_NODE; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 22 | this.ANCHOR_OFFSET; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 23 | this.FOCUS_OFFSET; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 24 | this.CONTENT_NODE; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 25 | this.SELECTION_STRING; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 26 | this.COLUMN_NAME_ORDER = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 27 | this.column_information; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 28 | this.LAST_ACTIVE_ELEMENT_ID = ''; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 29 | this.LAST_ACTIVE_ELEMENT_CURSOR = 0; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 30 | this.reset_zoom_setting = 14; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 31 | this.zoom_setting; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 32 | this.scroll_allowed = false; // Updated by the config | |
| 33 | this.load_target_page = 0; // USED FOR INJECTION - NOTE ASK TEAM ABOUT | |
| 34 | ||
| 35 | this.toolbar_allowed = true; // Default true, Provided by the config | |
| 36 | this.current_sorting = ''; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 37 | this.default_styles = {}; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 38 | this.contextmenu = true; // Default true, Provided by the config | |
| 39 | this.default_widths = {}; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 40 | this.configuration = {}; // Provided by the config | |
| 41 | ||
| 42 | // Saving the most recently deleted row for recovery | |
| 43 | this.recent_deletion = {}; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 44 | this.recent_deletion_index = -1; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 45 | this.recent_deletion_filtered_index = -1; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 46 | ||
| 47 | this.undo_delete_check = false; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 48 | this.grid_name; // Provided by the config | |
| 49 | this.save_filters_url; // Provided by the config | |
| 50 | this.load_filters_url; // Provided by the config | |
| 51 | this.check_highlight_row_url; // Provided by the config | |
| 52 | this.insert_row_url; // Provided by the config | |
| 53 | this.column_rename = {}; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 54 | this.grid_settings = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 55 | this.grid_filters = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 56 | this.excel_edit = false; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 57 | ||
| 58 | this.grid_widths = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 59 | //Pagination Values | |
| 60 | this.max_results; // Provided by the config | |
| 61 | this.max_pagination_links = ''; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 62 | this.pagination_selected_value = ''; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 63 | this.grid_filter_list = ''; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 64 | this.grid_filter_columns = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 65 | ||
| 66 | this.stylesheet; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 67 | this.stylesheet_injection; // Provided by the config | |
| 68 | this.data_url; // Provided by the config | |
| 69 | // Identifier Variables | |
| 70 | this.primary_key = ''; // Default empty, provided by the config | |
| 71 | this.url_identifier = ''; // Default empty, provided by the config | |
| 72 | // Context Menu Variables | |
| 73 | this.context_menu_injection = ''; // Default empty, provided by the config | |
| 74 | this.right_click_source_cell; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 75 | this.drag_increment_mode = false; // Default false, provided by the config | |
| 76 | this.excel_button = true; // Default true, provided by the config | |
| 77 | this.increment_button = true; // Default true, provided by the config | |
| 78 | this.drag_copy_features = true; // Default true, provided by the config | |
| 79 | this.filter_notification = true; // Default true, provided by the config | |
| 80 | this.zoom_button = true; // Default true, provided by the config | |
| 81 | this.highlight_checkbox; // Provided by the config | |
| 82 | this.highlight_checked; // Provided by the config | |
| 83 | // Table Data Manipulation Variables | |
| 84 | this.starting_cell; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 85 | this.ending_cell; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 86 | this.copy_lock = false; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 87 | this.modified_data = {}; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 88 | this.save_data_url = ''; // Default empty, provided by the config | |
| 89 | this.copy_element_array = []; // DO NOT CHANGE - SYSTEM USE ONLY | |
| 90 | } | |
| 91 | escape_json(new_json){ | |
| 92 | // Load Global JSON returns a string of text, that string needs to be stringified to properly escape quotes. | |
| 93 | // let new_json = JSON.stringify(json); | |
| 94 | // // Once stringified, we further escape special characters to prevent them from rendering in the dom. | |
| 95 | ||
| 96 | // new_json = new_json.replace(/\\f/g, '\\\\f'); | |
| 97 | // new_json = new_json.replace(/\\n/g, '\\\\n'); | |
| 98 | // new_json = new_json.replace(/\\r/g, '\\\\r'); | |
| 99 | // new_json = new_json.replace(/\\t/g, '\\\\t'); | |
| 100 | new_json = new_json.replace(/'/g, `\'`); | |
| 101 | new_json = new_json.replace(/"/g, `\\"`); | |
| 102 | // new_json = new_json.replace(/'/g, "\\\\'"); | |
| 103 | // To get the resulting string back into an ARRAY of JSON, we parse it once to get it back to a proper JSON string, then parse it again | |
| 104 | // To get it to properly evaluate as an array of json, not a broken json string. | |
| 105 | ||
| 106 | return JSON.parse(new_json); | |
| 107 | // return JSON.parse(JSON.parse(new_json)); | |
| 108 | } | |
| 109 | async load_global_json(url){ | |
| 110 | const response = await fetch(url); | |
| 111 | // Return the response as text instead of json, so we can escape special characters. | |
| 112 | const jsonData = response.text(); | |
| 113 | return jsonData; | |
| 114 | ||
| 115 | ||
| 116 | } | |
| 117 | async get_request(url){ | |
| 118 | const rawResponse = await fetch(url, { | |
| 119 | method: 'GET' | |
| 120 | }); | |
| 121 | const content = rawResponse.text(); | |
| 122 | return content; | |
| 123 | } | |
| 124 | async send_json(url, json){ | |
| 125 | const rawResponse = await fetch(url, { | |
| 126 | method: "POST", | |
| 127 | headers: { | |
| 128 | "Content-Type": "application/json", | |
| 129 | }, | |
| 130 | body: JSON.stringify(json), | |
| 131 | }); | |
| 132 | const content = rawResponse.text(); | |
| 133 | return content; | |
| 134 | } | |
| 135 | connectedCallback() { | |
| 136 | // Styling for the component, style sheets outside the component DO NOT affect the component. | |
| 137 | this.stylesheet = document.createElement('style'); | |
| 138 | ||
| 139 | if(this.highlight_checked){ | |
| 140 | this.stylesheet.innerHTML = ` | |
| 141 | .hyperlink-form-popup button{ | |
| 142 | user-select: none; | |
| 143 | } | |
| 144 | .disabled-mouse{ | |
| 145 | pointer-events: none; | |
| 146 | } | |
| 147 | a:hover{ | |
| 148 | cursor: pointer; | |
| 149 | } | |
| 150 | .hyperlink-form-hidden{ | |
| 151 | display: none; | |
| 152 | } | |
| 153 | .hyperlink-form-popup{ | |
| 154 | display: flex; | |
| 155 | background-color: #d3d3d3; | |
| 156 | flex-direction: column; | |
| 157 | align-items: center; | |
| 158 | justify-content: center; | |
| 159 | height: 200px; | |
| 160 | width: 200px; | |
| 161 | border: 1px solid black; | |
| 162 | border-radius: 10% 10% 10% 10%; | |
| 163 | position: absolute; | |
| 164 | z-index: 999999; | |
| 165 | top: 0px; | |
| 166 | left: 45%; | |
| 167 | } | |
| 168 | .zoom-button-container{ | |
| 169 | display: flex; | |
| 170 | align-items: center; | |
| 171 | justify-content: space-around; | |
| 172 | width: 175px; | |
| 173 | } | |
| 174 | .zoom-button-container >*:hover{ | |
| 175 | transform: scale(1.05); | |
| 176 | } | |
| 177 | .empty-json-container{ | |
| 178 | display: flex; | |
| 179 | flex-direction: column; | |
| 180 | width: 100%; | |
| 181 | align-items: center; | |
| 182 | justify-content: center; | |
| 183 | min-height: 300px; | |
| 184 | } | |
| 185 | .spinner-container * { | |
| 186 | margin-left: 10px; | |
| 187 | margin-right: 10px; | |
| 188 | } | |
| 189 | .spinner-container{ | |
| 190 | display: flex; | |
| 191 | flex-direction: row; | |
| 192 | align-items: center; | |
| 193 | justify-content: center; | |
| 194 | } | |
| 195 | .load-message{ | |
| 196 | font: 30px Arial; | |
| 197 | animation: breathe 3s linear infinite; | |
| 198 | } | |
| 199 | .spinner{ | |
| 200 | background: #FFFFFF; | |
| 201 | height: 30px; | |
| 202 | width: 30px; | |
| 203 | border-radius: 50%; | |
| 204 | border: 5px solid #0097d8; | |
| 205 | border-top: 5px solid #003057; | |
| 206 | animation: spin 2s linear infinite; | |
| 207 | } | |
| 208 | @keyframes spin { | |
| 209 | 0% { | |
| 210 | transform: rotate(0deg); | |
| 211 | } | |
| 212 | 100% { | |
| 213 | transform: rotate(360deg); | |
| 214 | ||
| 215 | } | |
| 216 | } | |
| 217 | @keyframes breathe { | |
| 218 | 0% { | |
| 219 | opacity: 100%; | |
| 220 | transform: scale(1.15); | |
| 221 | } | |
| 222 | 50% { | |
| 223 | transform: scale(0.95); | |
| 224 | opacity: 25%; | |
| 225 | } | |
| 226 | 100% { | |
| 227 | opacity: 100%; | |
| 228 | transform: scale(1.15); | |
| 229 | ||
| 230 | } | |
| 231 | } | |
| 232 | /*Rare case of important, we want this to override any background styling*/ | |
| 233 | .blink-field-red{ | |
| 234 | background-color: red!important; | |
| 235 | animation: blink 2s linear infinite; | |
| 236 | } | |
| 237 | @keyframes blink { | |
| 238 | 0% { | |
| 239 | opacity: 100%; | |
| 240 | } | |
| 241 | 50% { | |
| 242 | opacity: 25%; | |
| 243 | } | |
| 244 | 100% { | |
| 245 | opacity: 100%; | |
| 246 | ||
| 247 | } | |
| 248 | } | |
| 249 | table tr td:has(select.color-my-parent){ | |
| 250 | background: #ffff00; | |
| 251 | } | |
| 252 | table tr td:has(input.color-my-parent){ | |
| 253 | background: #ffff00; | |
| 254 | } | |
| 255 | .row_number{ | |
| 256 | color: #FFFFFF; | |
| 257 | position: sticky; | |
| 258 | left: 0px; | |
| 259 | background-color: #888888; | |
| 260 | z-index: 9998; | |
| 261 | font-size: var(--mod-text-size); | |
| 262 | } | |
| 263 | .row_number.highlight_error{ | |
| 264 | color: #888888; | |
| 265 | background-color: #ffe0e0; | |
| 266 | } | |
| 267 | .row_number.highlight_error2{ | |
| 268 | background-color: #992d28; | |
| 269 | } | |
| 270 | .row_number.highlight_error3{ | |
| 271 | background-color: #eb8a49; | |
| 272 | } | |
| 273 | thead{ | |
| 274 | position: sticky; | |
| 275 | left: 0px; | |
| 276 | z-index: 9999; | |
| 277 | margin-top: -1px; | |
| 278 | } | |
| 279 | .super_header_row{ | |
| 280 | display: flex; | |
| 281 | flex-direction: row; | |
| 282 | border: solid 1px #dddddd; | |
| 283 | position: sticky; | |
| 284 | top: 0px; | |
| 285 | left: 0px; | |
| 286 | z-index: 9999; | |
| 287 | } | |
| 288 | .super_header{ | |
| 289 | text-align: left; | |
| 290 | border-left-width: 0px; | |
| 291 | padding-left: 0px; | |
| 292 | padding-right: 0px; | |
| 293 | ||
| 294 | color: #E3E3E3; | |
| 295 | background-color: #666666; | |
| 296 | height: 35px; | |
| 297 | } | |
| 298 | .super_header::before{ | |
| 299 | content: attr(super-header-name); | |
| 300 | position: sticky; | |
| 301 | left: 15px; | |
| 302 | top: 10px; | |
| 303 | margin-left: 15px; | |
| 304 | font: 15px Arial; | |
| 305 | font-weight: normal; | |
| 306 | } | |
| 307 | .icon_tooltip_container{ | |
| 308 | display: flex; | |
| 309 | flex-direction: row; | |
| 310 | align-items: center; | |
| 311 | justify-content: flex-start; | |
| 312 | } | |
| 313 | .icon_container{ | |
| 314 | display: flex; | |
| 315 | flex-direction: row; | |
| 316 | align-items: center; | |
| 317 | justify-content: center; | |
| 318 | flex-wrap: wrap; | |
| 319 | margin-right: 10px; | |
| 320 | } | |
| 321 | .increment-button{ | |
| 322 | border-radius: 2px; | |
| 323 | background-color: #f5f5f5; | |
| 324 | padding: 0px 10px 0px 10px; | |
| 325 | font: 12px Verdana, Geneva, Arial, Helvetica, sans-serif; | |
| 326 | cursor: pointer; | |
| 327 | color: #555555; | |
| 328 | border: 1px solid #aeaeae; | |
| 329 | height: 25px; | |
| 330 | } | |
| 331 | .increment-button:hover{ | |
| 332 | transform: scale(1.05); | |
| 333 | cursor: pointer; | |
| 334 | } | |
| 335 | .increment-toggle{ | |
| 336 | background-color: #ffaaaa; | |
| 337 | } | |
| 338 | .tooltips{ | |
| 339 | display: flex; | |
| 340 | flex-direction: column; | |
| 341 | align-items: flex-start; | |
| 342 | justify-content: center; | |
| 343 | } | |
| 344 | .tooltips p{ | |
| 345 | margin-top: 0px; | |
| 346 | margin-bottom: 0px; | |
| 347 | } | |
| 348 | .toolbar_container > * { | |
| 349 | margin-left: 3px; | |
| 350 | margin-right: 3px; | |
| 351 | } | |
| 352 | .no_data_container{ | |
| 353 | display: flex; | |
| 354 | flex-direction: column; | |
| 355 | align-items: center; | |
| 356 | justify-content: center; | |
| 357 | } | |
| 358 | .highlight_section > * { | |
| 359 | margin-left: 2px; | |
| 360 | margin-right: 2px; | |
| 361 | margin-top: 0px; | |
| 362 | margin-bottom: 0px; | |
| 363 | } | |
| 364 | .highlight_section{ | |
| 365 | display: flex; | |
| 366 | align-items: center; | |
| 367 | justify-content: space-evenly; | |
| 368 | } | |
| 369 | .filter_dot_none{ | |
| 370 | height: 20px; | |
| 371 | width: 20px; | |
| 372 | display: flex; | |
| 373 | align-items: center; | |
| 374 | justify-content: center; | |
| 375 | padding-bottom: 3px; | |
| 376 | font-size: 16px; | |
| 377 | background-color: #95cf8c; | |
| 378 | border-radius: 30%; | |
| 379 | } | |
| 380 | .filter_dot_active{ | |
| 381 | height: 20px; | |
| 382 | width: 20px; | |
| 383 | display: flex; | |
| 384 | align-items: center; | |
| 385 | justify-content: center; | |
| 386 | padding-bottom: 3px; | |
| 387 | font-size: 16px; | |
| 388 | background-color: #cf8c8c; | |
| 389 | border-radius: 30%; | |
| 390 | } | |
| 391 | .filter_notification > * { | |
| 392 | margin-left: 2px; | |
| 393 | margin-right: 2px; | |
| 394 | margin-top: 0px; | |
| 395 | margin-bottom: 0px; | |
| 396 | } | |
| 397 | .filter_notification{ | |
| 398 | display: flex; | |
| 399 | align-items: center; | |
| 400 | justify-content: space-evenly; | |
| 401 | } | |
| 402 | .table_container{ | |
| 403 | display: flex; | |
| 404 | flex-direction: column; | |
| 405 | overflow-y: auto; | |
| 406 | min-height: 20em; | |
| 407 | max-height: calc(97lvh - 500px); | |
| 408 | overflow-x: scroll; | |
| 409 | } | |
| 410 | .toolbar_container{ | |
| 411 | display: flex; | |
| 412 | flex-direction: row; | |
| 413 | padding-top: 5px; | |
| 414 | padding-bottom: 5px; | |
| 415 | align-items: center; | |
| 416 | justify-content: space-between; | |
| 417 | border-bottom: 2px solid #666666; | |
| 418 | } | |
| 419 | .grid-icon{ | |
| 420 | max-width: 50px; | |
| 421 | } | |
| 422 | .grid-icon:hover{ | |
| 423 | max-width: 50px; | |
| 424 | transform: scale(1.1); | |
| 425 | cursor: pointer; | |
| 426 | } | |
| 427 | .active_filter{ | |
| 428 | background-color: #828267; | |
| 429 | } | |
| 430 | @keyframes slideOutFromLeft { | |
| 431 | 0% { | |
| 432 | transform: translateX(0); | |
| 433 | } | |
| 434 | 50% { | |
| 435 | transform: translateX(50%); | |
| 436 | } | |
| 437 | 100% { | |
| 438 | transform: translateX(100%); | |
| 439 | ||
| 440 | } | |
| 441 | } | |
| 442 | @keyframes slideInFromLeft { | |
| 443 | 0% { | |
| 444 | transform: translateX(-100%); | |
| 445 | } | |
| 446 | 100% { | |
| 447 | transform: translateX(0); | |
| 448 | } | |
| 449 | } | |
| 450 | .grid_table_data:active .copy_drag{ | |
| 451 | display: inline-block; | |
| 452 | color: red; | |
| 453 | } | |
| 454 | table:not(.grid-dragging) .grid_table_data:hover .copy_drag{ | |
| 455 | display: inline-block; | |
| 456 | color: #bad7c0; | |
| 457 | } | |
| 458 | td.copied_over_wrong{ | |
| 459 | background-color: #ffaaaa; | |
| 460 | } | |
| 461 | td.copied_over{ | |
| 462 | background-color: #bad7c0; | |
| 463 | } | |
| 464 | td .copy_drag{ | |
| 465 | font: 28px Arial; | |
| 466 | width: 1vw; | |
| 467 | ||
| 468 | position: relative; | |
| 469 | vertical-align: middle; | |
| 470 | display: none; | |
| 471 | ||
| 472 | } | |
| 473 | td .copy_drag_inner{ | |
| 474 | /* | |
| 475 | position: absolute; | |
| 476 | right: -1px; | |
| 477 | */ | |
| 478 | } | |
| 479 | td .copy_drag_inner:hover{ | |
| 480 | /* | |
| 481 | position: absolute; | |
| 482 | right: -1px; | |
| 483 | font: 30px Arial; | |
| 484 | */ | |
| 485 | cursor: grab; | |
| 486 | ||
| 487 | } | |
| 488 | .confirmation_container{ | |
| 489 | /* display: flex; | |
| 490 | flex-direction: column; | |
| 491 | justify-content: space-around; | |
| 492 | align-items: center; */ | |
| 493 | position: absolute; | |
| 494 | left: 0%; | |
| 495 | top: 0%; | |
| 496 | z-index: 99999; | |
| 497 | width: 100%; | |
| 498 | height: 100%; | |
| 499 | background-color: rgba(123, 133, 182, 0.19); | |
| 500 | } | |
| 501 | .confirmation_container_display{ | |
| 502 | display: flex; | |
| 503 | flex-direction: column; | |
| 504 | width: 20vw; | |
| 505 | min-height: 10vw; | |
| 506 | background-color: #c3c3c3; | |
| 507 | padding: 1vw 1vw 1vw 1vw; | |
| 508 | border-radius: 1vw; | |
| 509 | left: calc(50vw - 10vw); | |
| 510 | position: absolute; | |
| 511 | } | |
| 512 | .confirmation_button_container{ | |
| 513 | display: flex; | |
| 514 | flex-direction: row; | |
| 515 | width: 100%; | |
| 516 | height: 15%; | |
| 517 | justify-content: space-between; | |
| 518 | align-items: center; | |
| 519 | } | |
| 520 | .confirmation_button_container button{ | |
| 521 | width: 75px; | |
| 522 | } | |
| 523 | .context-menu{ | |
| 524 | z-index: 10000; | |
| 525 | position: fixed; | |
| 526 | background: #c3c3c3; | |
| 527 | color: #555; | |
| 528 | font-family: sans-serif; | |
| 529 | font-size: 11px; | |
| 530 | -webkit-transition: opacity .5s ease-in-out; | |
| 531 | -moz-transition: opacity .5s ease-in-out; | |
| 532 | -ms-transition: opacity .5s ease-in-out; | |
| 533 | -o-transition: opacity .5s ease-in-out; | |
| 534 | transition: opacity .5s ease-in-out; | |
| 535 | -webkit-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); | |
| 536 | -moz-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); | |
| 537 | box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); | |
| 538 | padding: 0px; | |
| 539 | border: 1px solid #C6C6C6; | |
| 540 | } | |
| 541 | .context-menu a { | |
| 542 | display: block; | |
| 543 | color: #555; | |
| 544 | text-decoration: none; | |
| 545 | padding: 6px 8px 6px 30px; | |
| 546 | width: 250px; | |
| 547 | position: relative; | |
| 548 | } | |
| 549 | ||
| 550 | .context-menu a span { | |
| 551 | color: #BCB1B3; | |
| 552 | float: right; | |
| 553 | } | |
| 554 | ||
| 555 | .context-menu a:hover { | |
| 556 | color: #fff; | |
| 557 | background: #3879D9; | |
| 558 | } | |
| 559 | ||
| 560 | .context-menu hr { | |
| 561 | border: 1px solid #EBEBEB; | |
| 562 | border-bottom: 0; | |
| 563 | } | |
| 564 | .grid_table{ | |
| 565 | border: solid 1px #dddddd; | |
| 566 | border-collapse: collapse; | |
| 567 | padding: 2px 3px; | |
| 568 | text-align: center; | |
| 569 | table-layout: fixed; | |
| 570 | width: 100%; | |
| 571 | } | |
| 572 | ||
| 573 | .grid_table_header{ | |
| 574 | padding: 5px; | |
| 575 | font: 12px Arial; | |
| 576 | font-weight: normal; | |
| 577 | color: #E3E3E3; | |
| 578 | background-color: #666666; | |
| 579 | height: 35px; | |
| 580 | border: solid 1px #dddddd; | |
| 581 | border-collapse: collapse; | |
| 582 | text-align: center; | |
| 583 | } | |
| 584 | .grid_table_header form{ | |
| 585 | display: flex; | |
| 586 | justify-content: flex-end; | |
| 587 | } | |
| 588 | .grid_table_header form div:not(.grid_menu):not(.ignore_overflow):not(.grid_menu_options):not(.suggestion-container) { | |
| 589 | text-overflow: ellipsis; | |
| 590 | overflow: hidden; | |
| 591 | white-space: nowrap; | |
| 592 | } | |
| 593 | .excel_table_header{ | |
| 594 | padding: 5px; | |
| 595 | font: 12px Arial; | |
| 596 | font-weight: normal; | |
| 597 | color: #E3E3E3; | |
| 598 | background-color: #217346; | |
| 599 | height: 35px; | |
| 600 | border: solid 1px #dddddd; | |
| 601 | border-collapse: collapse; | |
| 602 | text-align: center; | |
| 603 | } | |
| 604 | .excel_table_header form{ | |
| 605 | display: flex; | |
| 606 | justify-content: flex-end; | |
| 607 | } | |
| 608 | .excel_table_header form div:not(.grid_menu):not(.ignore_overflow):not(.grid_menu_options) { | |
| 609 | text-overflow: ellipsis; | |
| 610 | overflow: hidden; | |
| 611 | white-space: nowrap; | |
| 612 | } | |
| 613 | .grid_table_data{ | |
| 614 | ||
| 615 | height: inherit; | |
| 616 | font: var(--mod-text-size) Arial; | |
| 617 | border: solid 1px #dddddd; | |
| 618 | border-collapse: collapse; | |
| 619 | padding: calc(var(--mod-text-size) / 4); | |
| 620 | text-align: center; | |
| 621 | } | |
| 622 | .grid_table_data .cell-data-wrapper{ | |
| 623 | text-overflow: ellipsis; | |
| 624 | text-wrap: nowrap; | |
| 625 | overflow: hidden; | |
| 626 | width: 90%; | |
| 627 | } | |
| 628 | .grid_table_data :is(select,input){ | |
| 629 | text-overflow: ellipsis; | |
| 630 | text-wrap: nowrap; | |
| 631 | overflow: hidden; | |
| 632 | } | |
| 633 | .grid_table_data textarea{ | |
| 634 | overflow: hidden; | |
| 635 | } | |
| 636 | .grid_table_data .contentarea{ | |
| 637 | overflow: hidden; | |
| 638 | font-size: var(--mod-text-size); | |
| 639 | ||
| 640 | } | |
| 641 | .grid_table_data .contentarea a:visited{ | |
| 642 | color: #336699; | |
| 643 | } | |
| 644 | .grid_table_data .contentarea a{ | |
| 645 | color: #336699; | |
| 646 | } | |
| 647 | .grid_table_data .contentarea:has(a){ | |
| 648 | text-decoration: underline; | |
| 649 | color: #336699; | |
| 650 | -webkit-user-modify: read-write-plaintext-only; | |
| 651 | overflow-wrap: break-word; | |
| 652 | -webkit-line-break: after-white-space; | |
| 653 | cursor: pointer; | |
| 654 | } | |
| 655 | .grid_table_data > * { | |
| 656 | display: inline-block; | |
| 657 | margin-left: 5px; | |
| 658 | margin-right: 5px; | |
| 659 | vertical-align: middle; | |
| 660 | } | |
| 661 | tr .grid_table_data > *:not(div) { | |
| 662 | height: calc(var(--mod-text-size) + 4px); | |
| 663 | display: inline-block; | |
| 664 | font-size: var(--mod-text-size); | |
| 665 | } | |
| 666 | ||
| 667 | .grid_menu{ | |
| 668 | position: relative; | |
| 669 | top: -1px; | |
| 670 | left: -4px; | |
| 671 | float: left; | |
| 672 | padding: 0px 3px 0px 8px; | |
| 673 | color: #CCCCCC; | |
| 674 | z-index:1; | |
| 675 | } | |
| 676 | .grid_menu:hover{ | |
| 677 | color: #FFFFFF; | |
| 678 | } | |
| 679 | .grid_menu_options{ | |
| 680 | position:absolute; | |
| 681 | width:calc(100% - 10px); | |
| 682 | } | |
| 683 | .grid_menu_options:hover{ | |
| 684 | background-color: #D3D3D3; | |
| 685 | } | |
| 686 | .filter_box{ | |
| 687 | position: relative; | |
| 688 | width: 260px; | |
| 689 | height: 220px; | |
| 690 | border: 1px solid #888888; | |
| 691 | background-color: #FFFFFF; | |
| 692 | color: #000000; | |
| 693 | padding: 5px; | |
| 694 | text-align: left; | |
| 695 | align: left; | |
| 696 | float: left; | |
| 697 | z-index: 3; | |
| 698 | } | |
| 699 | .pagination_container{ | |
| 700 | margin-left: 5px; | |
| 701 | margin-top: 5px; | |
| 702 | margin-bottom: 5px; | |
| 703 | border-top: 2px solid #666666; | |
| 704 | } | |
| 705 | .pagination_area{ | |
| 706 | position: relative; | |
| 707 | float: left; | |
| 708 | font: 14px Arial; | |
| 709 | color: #666666; | |
| 710 | } | |
| 711 | .pagination_links{ | |
| 712 | position: relative; | |
| 713 | top: 2px; | |
| 714 | font: 14px Arial; | |
| 715 | color: #666666; | |
| 716 | text-decoration:none; | |
| 717 | margin-left: 2px; | |
| 718 | margin-right: 2px; | |
| 719 | } | |
| 720 | ||
| 721 | .pagination_block{ | |
| 722 | position: relative; | |
| 723 | top : 2px; | |
| 724 | border: 1px solid #666666; | |
| 725 | padding: 0px 5px 0px 5px; | |
| 726 | } | |
| 727 | ||
| 728 | .pagination_selected_link{ | |
| 729 | font-weight: bold; | |
| 730 | font: 20px Arial; | |
| 731 | color: #444444; | |
| 732 | /* text-decoration: underline; */ | |
| 733 | } | |
| 734 | ||
| 735 | .pagination_page_number_textbox{ | |
| 736 | position: relative; | |
| 737 | width: 60px; | |
| 738 | } | |
| 739 | ||
| 740 | .max_results_per_page{ | |
| 741 | position: relative; | |
| 742 | float: left; | |
| 743 | top: 5px; | |
| 744 | left: 5px; | |
| 745 | font: 14px Arial; | |
| 746 | color: #666666; | |
| 747 | } | |
| 748 | ||
| 749 | .round{ | |
| 750 | -webkit-border-radius: 3px; | |
| 751 | -moz-border-radius: 3px; | |
| 752 | border-radius: 3px; | |
| 753 | } | |
| 754 | .grid_tr_alternating tr:nth-child(odd):not([class^=""]){ | |
| 755 | height:1px; | |
| 756 | color: #999999; | |
| 757 | border: 1px solid #F2F2F2; | |
| 758 | background-color:#EEEEEE; | |
| 759 | } | |
| 760 | .grid_tr_alternating tr:nth-child(even){ | |
| 761 | height:1px; | |
| 762 | color: #999999; | |
| 763 | border: 1px solid #ededed; | |
| 764 | background-color:#F2F2F2; | |
| 765 | } | |
| 766 | ||
| 767 | .grid_tr_alternating tr:nth-child(odd):hover{ | |
| 768 | color: #FFFFFF; | |
| 769 | border: 1px solid #ededed; | |
| 770 | background-color: #888888; | |
| 771 | } | |
| 772 | .grid_tr_alternating tr:hover td.grid_table_data{ | |
| 773 | color: #FFFFFF; | |
| 774 | border: 1px solid #ededed; | |
| 775 | background-color: #888888; | |
| 776 | } | |
| 777 | ||
| 778 | .grid_data_cells{ | |
| 779 | height:18px; | |
| 780 | position:relative; | |
| 781 | font: 12px Arial; | |
| 782 | font-weight: normal; | |
| 783 | text-align:left; | |
| 784 | padding-left: 5px; | |
| 785 | } | |
| 786 | ||
| 787 | .grid_standard_td{ | |
| 788 | color: #999999; | |
| 789 | background-color:#FFFFFF; | |
| 790 | } | |
| 791 | .table th { | |
| 792 | position: relative; | |
| 793 | } | |
| 794 | .resizer { | |
| 795 | position: absolute; | |
| 796 | top: 0; | |
| 797 | right: 0; | |
| 798 | width: 5px; | |
| 799 | cursor: col-resize; | |
| 800 | user-select: none; | |
| 801 | height: 100%; | |
| 802 | } | |
| 803 | .resizer:hover, | |
| 804 | ||
| 805 | .resizing { | |
| 806 | border-right: 2px solid #333333; | |
| 807 | } | |
| 808 | ||
| 809 | .resizable { | |
| 810 | border: 1px solid gray; | |
| 811 | height: 100px; | |
| 812 | width: 100px; | |
| 813 | position: relative; | |
| 814 | } | |
| 815 | .cell_red{ | |
| 816 | font: 12px Arial; | |
| 817 | text-align: center; | |
| 818 | color: #FF0000; | |
| 819 | background-color:#CCCCCC; | |
| 820 | } | |
| 821 | .cell_red:hover{ | |
| 822 | color: #FFFFFF; | |
| 823 | background-color:#444444; | |
| 824 | } | |
| 825 | .cell_link{ | |
| 826 | font: 12px Arial; | |
| 827 | text-align: center; | |
| 828 | text-decoration: underline; | |
| 829 | color: #336699; | |
| 830 | } | |
| 831 | .cell_link:hover{ | |
| 832 | font: 12px Arial; | |
| 833 | text-align: center; | |
| 834 | text-decoration: underline; | |
| 835 | color: #000000; | |
| 836 | } | |
| 837 | .cell_green{ | |
| 838 | font: 12px Arial; | |
| 839 | text-align: center; | |
| 840 | color: #666666; | |
| 841 | background-color:#ccff99; | |
| 842 | } | |
| 843 | .cell_green:hover{ | |
| 844 | color: #FFFFFF; | |
| 845 | background-color:#444444; | |
| 846 | } | |
| 847 | .cell_yellow{ | |
| 848 | font: 12px Arial; | |
| 849 | font-weight: bold; | |
| 850 | text-align: center; | |
| 851 | color: #666666; | |
| 852 | background-color:#ffffcc; | |
| 853 | } | |
| 854 | .start-hidden-row{ | |
| 855 | display: none; | |
| 856 | } | |
| 857 | .end-hidden-row{ | |
| 858 | display: none; | |
| 859 | } | |
| 860 | .suggestion-popup{ | |
| 861 | width: 200px; | |
| 862 | background-color: #FAFAFA; | |
| 863 | padding: 10px 10px 10px 10px; | |
| 864 | z-index: 999999; | |
| 865 | position: fixed; | |
| 866 | box-shadow: 1px 1px 1px rgba(0,0,0,0.8), inset 0px 1px 1px rgba(0, 0, 25, .3), inset -1px -1px 2px rgba(0, 0, 25, .3); | |
| 867 | } | |
| 868 | .grid_table_header .suggestion-popup .suggestion-container{ | |
| 869 | overflow-y: scroll; | |
| 870 | overflow-x: hidden; | |
| 871 | max-height: 100px; | |
| 872 | } | |
| 873 | .suggestion-item{ | |
| 874 | display: flex; | |
| 875 | align-items: center; | |
| 876 | justify-content: flex-start; | |
| 877 | border-bottom: 1px dashed black; | |
| 878 | } | |
| 879 | .suggestion-item:hover{ | |
| 880 | background-color: #a3a3a3; | |
| 881 | } | |
| 882 | .suggestion-title{ | |
| 883 | margin-top: 0px; | |
| 884 | margin-bottom: 0px; | |
| 885 | display: flex; | |
| 886 | align-items: center; | |
| 887 | justify-content: center; | |
| 888 | font-weight: bold; | |
| 889 | } | |
| 890 | .suggestion-link::before{ | |
| 891 | content: "\\1f517"; | |
| 892 | } | |
| 893 | `; | |
| 894 | }else{ | |
| 895 | this.stylesheet.innerHTML = ` | |
| 896 | .hyperlink-form-popup button{ | |
| 897 | user-select: none; | |
| 898 | } | |
| 899 | .disabled-mouse{ | |
| 900 | pointer-events: none; | |
| 901 | } | |
| 902 | a:hover{ | |
| 903 | cursor: pointer; | |
| 904 | } | |
| 905 | .hyperlink-form-hidden{ | |
| 906 | display: none; | |
| 907 | } | |
| 908 | .hyperlink-form-popup{ | |
| 909 | display: flex; | |
| 910 | background-color: #d3d3d3; | |
| 911 | flex-direction: column; | |
| 912 | align-items: center; | |
| 913 | justify-content: center; | |
| 914 | height: 200px; | |
| 915 | width: 200px; | |
| 916 | border: 1px solid black; | |
| 917 | border-radius: 10% 10% 10% 10%; | |
| 918 | position: absolute; | |
| 919 | z-index: 999999; | |
| 920 | top: 0px; | |
| 921 | left: 45%; | |
| 922 | } | |
| 923 | .zoom-button-container{ | |
| 924 | display: flex; | |
| 925 | align-items: center; | |
| 926 | justify-content: space-around; | |
| 927 | width: 175px; | |
| 928 | } | |
| 929 | .zoom-button-container >*:hover{ | |
| 930 | transform: scale(1.05); | |
| 931 | } | |
| 932 | .empty-json-container{ | |
| 933 | display: flex; | |
| 934 | flex-direction: column; | |
| 935 | width: 100%; | |
| 936 | align-items: center; | |
| 937 | justify-content: center; | |
| 938 | min-height: 300px; | |
| 939 | } | |
| 940 | .spinner-container * { | |
| 941 | margin-left: 10px; | |
| 942 | margin-right: 10px; | |
| 943 | } | |
| 944 | .spinner-container{ | |
| 945 | display: flex; | |
| 946 | flex-direction: row; | |
| 947 | align-items: center; | |
| 948 | justify-content: center; | |
| 949 | } | |
| 950 | .load-message{ | |
| 951 | font: 30px Arial; | |
| 952 | animation: breathe 3s linear infinite; | |
| 953 | } | |
| 954 | .spinner{ | |
| 955 | background: #FFFFFF; | |
| 956 | height: 30px; | |
| 957 | width: 30px; | |
| 958 | border-radius: 50%; | |
| 959 | border: 5px solid #0097d8; | |
| 960 | border-top: 5px solid #003057; | |
| 961 | animation: spin 2s linear infinite; | |
| 962 | } | |
| 963 | @keyframes spin { | |
| 964 | 0% { | |
| 965 | transform: rotate(0deg); | |
| 966 | } | |
| 967 | 100% { | |
| 968 | transform: rotate(360deg); | |
| 969 | ||
| 970 | } | |
| 971 | } | |
| 972 | @keyframes breathe { | |
| 973 | 0% { | |
| 974 | opacity: 100%; | |
| 975 | transform: scale(1.15); | |
| 976 | } | |
| 977 | 50% { | |
| 978 | transform: scale(0.95); | |
| 979 | opacity: 25%; | |
| 980 | } | |
| 981 | 100% { | |
| 982 | opacity: 100%; | |
| 983 | transform: scale(1.15); | |
| 984 | ||
| 985 | } | |
| 986 | } | |
| 987 | /*Rare case of important, we want this to override any background styling*/ | |
| 988 | .blink-field-red{ | |
| 989 | background-color: red!important; | |
| 990 | animation: blink 2s linear infinite; | |
| 991 | } | |
| 992 | @keyframes blink { | |
| 993 | 0% { | |
| 994 | opacity: 100%; | |
| 995 | } | |
| 996 | 50% { | |
| 997 | opacity: 25%; | |
| 998 | } | |
| 999 | 100% { | |
| 1000 | opacity: 100%; | |
| 1001 | ||
| 1002 | } | |
| 1003 | } | |
| 1004 | table tr td:has(select.color-my-parent){ | |
| 1005 | background: #ffff00; | |
| 1006 | } | |
| 1007 | table tr td:has(input.color-my-parent){ | |
| 1008 | background: #ffff00; | |
| 1009 | } | |
| 1010 | .row_number{ | |
| 1011 | color: #FFFFFF; | |
| 1012 | position: sticky; | |
| 1013 | left: 0px; | |
| 1014 | background-color: #888888; | |
| 1015 | z-index: 9998; | |
| 1016 | font-size: var(--mod-text-size); | |
| 1017 | } | |
| 1018 | .row_number.highlight_error{ | |
| 1019 | color: #888888; | |
| 1020 | background-color: #ffe0e0; | |
| 1021 | } | |
| 1022 | .row_number.highlight_error2{ | |
| 1023 | background-color: #992d28; | |
| 1024 | } | |
| 1025 | .row_number.highlight_error3{ | |
| 1026 | background-color: #eb8a49; | |
| 1027 | } | |
| 1028 | thead{ | |
| 1029 | position: sticky; | |
| 1030 | left: 0px; | |
| 1031 | z-index: 9999; | |
| 1032 | margin-top: -1px; | |
| 1033 | } | |
| 1034 | .super_header_row{ | |
| 1035 | display: flex; | |
| 1036 | flex-direction: row; | |
| 1037 | border: solid 1px #dddddd; | |
| 1038 | position: sticky; | |
| 1039 | top: 0px; | |
| 1040 | left: 0px; | |
| 1041 | z-index: 9999; | |
| 1042 | } | |
| 1043 | .super_header{ | |
| 1044 | text-align: left; | |
| 1045 | border-left-width: 0px; | |
| 1046 | padding-left: 0px; | |
| 1047 | padding-right: 0px; | |
| 1048 | ||
| 1049 | color: #E3E3E3; | |
| 1050 | background-color: #666666; | |
| 1051 | height: 35px; | |
| 1052 | } | |
| 1053 | .super_header::before{ | |
| 1054 | content: attr(super-header-name); | |
| 1055 | position: sticky; | |
| 1056 | left: 15px; | |
| 1057 | top: 10px; | |
| 1058 | margin-left: 15px; | |
| 1059 | font: 15px Arial; | |
| 1060 | font-weight: normal; | |
| 1061 | } | |
| 1062 | .icon_tooltip_container{ | |
| 1063 | display: flex; | |
| 1064 | flex-direction: row; | |
| 1065 | align-items: center; | |
| 1066 | justify-content: flex-start; | |
| 1067 | } | |
| 1068 | .icon_container{ | |
| 1069 | display: flex; | |
| 1070 | flex-direction: row; | |
| 1071 | align-items: center; | |
| 1072 | justify-content: center; | |
| 1073 | flex-wrap: wrap; | |
| 1074 | margin-right: 10px; | |
| 1075 | } | |
| 1076 | .increment-button{ | |
| 1077 | border-radius: 2px; | |
| 1078 | background-color: #f5f5f5; | |
| 1079 | padding: 0px 10px 0px 10px; | |
| 1080 | font: 12px Verdana, Geneva, Arial, Helvetica, sans-serif; | |
| 1081 | cursor: pointer; | |
| 1082 | color: #555555; | |
| 1083 | border: 1px solid #aeaeae; | |
| 1084 | height: 25px; | |
| 1085 | } | |
| 1086 | .increment-button:hover{ | |
| 1087 | transform: scale(1.05); | |
| 1088 | cursor: pointer; | |
| 1089 | } | |
| 1090 | .increment-toggle{ | |
| 1091 | background-color: #ffaaaa; | |
| 1092 | } | |
| 1093 | .tooltips{ | |
| 1094 | display: flex; | |
| 1095 | flex-direction: column; | |
| 1096 | align-items: flex-start; | |
| 1097 | justify-content: center; | |
| 1098 | } | |
| 1099 | .tooltips p{ | |
| 1100 | margin-top: 0px; | |
| 1101 | margin-bottom: 0px; | |
| 1102 | } | |
| 1103 | .toolbar_container > * { | |
| 1104 | margin-left: 3px; | |
| 1105 | margin-right: 3px; | |
| 1106 | } | |
| 1107 | .no_data_container{ | |
| 1108 | display: flex; | |
| 1109 | flex-direction: column; | |
| 1110 | align-items: center; | |
| 1111 | justify-content: center; | |
| 1112 | } | |
| 1113 | .highlight_section > * { | |
| 1114 | margin-left: 2px; | |
| 1115 | margin-right: 2px; | |
| 1116 | margin-top: 0px; | |
| 1117 | margin-bottom: 0px; | |
| 1118 | } | |
| 1119 | .highlight_section{ | |
| 1120 | display: flex; | |
| 1121 | align-items: center; | |
| 1122 | justify-content: space-evenly; | |
| 1123 | } | |
| 1124 | .filter_dot_none{ | |
| 1125 | height: 20px; | |
| 1126 | width: 20px; | |
| 1127 | display: flex; | |
| 1128 | align-items: center; | |
| 1129 | justify-content: center; | |
| 1130 | padding-bottom: 3px; | |
| 1131 | font-size: 16px; | |
| 1132 | background-color: #95cf8c; | |
| 1133 | border-radius: 30%; | |
| 1134 | } | |
| 1135 | .filter_dot_active{ | |
| 1136 | height: 20px; | |
| 1137 | width: 20px; | |
| 1138 | display: flex; | |
| 1139 | align-items: center; | |
| 1140 | justify-content: center; | |
| 1141 | padding-bottom: 3px; | |
| 1142 | font-size: 16px; | |
| 1143 | background-color: #cf8c8c; | |
| 1144 | border-radius: 30%; | |
| 1145 | } | |
| 1146 | .filter_notification > * { | |
| 1147 | margin-left: 2px; | |
| 1148 | margin-right: 2px; | |
| 1149 | margin-top: 0px; | |
| 1150 | margin-bottom: 0px; | |
| 1151 | } | |
| 1152 | .filter_notification{ | |
| 1153 | display: flex; | |
| 1154 | align-items: center; | |
| 1155 | justify-content: space-evenly; | |
| 1156 | } | |
| 1157 | .table_container{ | |
| 1158 | display: flex; | |
| 1159 | flex-direction: column; | |
| 1160 | overflow-y: auto; | |
| 1161 | min-height: 20em; | |
| 1162 | max-height: calc(97lvh - 500px); | |
| 1163 | overflow-x: scroll; | |
| 1164 | } | |
| 1165 | .toolbar_container{ | |
| 1166 | display: flex; | |
| 1167 | flex-direction: row; | |
| 1168 | padding-top: 5px; | |
| 1169 | padding-bottom: 5px; | |
| 1170 | align-items: center; | |
| 1171 | justify-content: space-between; | |
| 1172 | border-bottom: 2px solid #666666; | |
| 1173 | } | |
| 1174 | .grid-icon{ | |
| 1175 | max-width: 50px; | |
| 1176 | } | |
| 1177 | .grid-icon:hover{ | |
| 1178 | max-width: 50px; | |
| 1179 | transform: scale(1.1); | |
| 1180 | cursor: pointer; | |
| 1181 | } | |
| 1182 | .active_filter{ | |
| 1183 | background-color: #828267; | |
| 1184 | } | |
| 1185 | @keyframes slideOutFromLeft { | |
| 1186 | 0% { | |
| 1187 | transform: translateX(0); | |
| 1188 | } | |
| 1189 | 50% { | |
| 1190 | transform: translateX(50%); | |
| 1191 | } | |
| 1192 | 100% { | |
| 1193 | transform: translateX(100%); | |
| 1194 | ||
| 1195 | } | |
| 1196 | } | |
| 1197 | @keyframes slideInFromLeft { | |
| 1198 | 0% { | |
| 1199 | transform: translateX(-100%); | |
| 1200 | } | |
| 1201 | 100% { | |
| 1202 | transform: translateX(0); | |
| 1203 | } | |
| 1204 | } | |
| 1205 | .grid_table_data:active .copy_drag{ | |
| 1206 | display: inline-block; | |
| 1207 | color: red; | |
| 1208 | } | |
| 1209 | table:not(.grid-dragging) .grid_table_data:hover .copy_drag{ | |
| 1210 | display: inline-block; | |
| 1211 | color: #bad7c0; | |
| 1212 | } | |
| 1213 | td.copied_over_wrong{ | |
| 1214 | background-color: #ffaaaa; | |
| 1215 | } | |
| 1216 | td.copied_over{ | |
| 1217 | background-color: #bad7c0; | |
| 1218 | } | |
| 1219 | td .copy_drag{ | |
| 1220 | font: 28px Arial; | |
| 1221 | width: 1vw; | |
| 1222 | ||
| 1223 | position: relative; | |
| 1224 | vertical-align: middle; | |
| 1225 | display: none; | |
| 1226 | ||
| 1227 | } | |
| 1228 | td .copy_drag_inner{ | |
| 1229 | /* | |
| 1230 | position: absolute; | |
| 1231 | right: -1px; | |
| 1232 | */ | |
| 1233 | } | |
| 1234 | td .copy_drag_inner:hover{ | |
| 1235 | /* | |
| 1236 | position: absolute; | |
| 1237 | right: -1px; | |
| 1238 | font: 30px Arial; | |
| 1239 | */ | |
| 1240 | cursor: grab; | |
| 1241 | ||
| 1242 | } | |
| 1243 | .confirmation_container{ | |
| 1244 | /* display: flex; | |
| 1245 | flex-direction: column; | |
| 1246 | justify-content: space-around; | |
| 1247 | align-items: center; */ | |
| 1248 | position: absolute; | |
| 1249 | left: 0%; | |
| 1250 | top: 0%; | |
| 1251 | z-index: 99999; | |
| 1252 | width: 100%; | |
| 1253 | height: 100%; | |
| 1254 | background-color: rgba(123, 133, 182, 0.19); | |
| 1255 | } | |
| 1256 | .confirmation_container_display{ | |
| 1257 | display: flex; | |
| 1258 | flex-direction: column; | |
| 1259 | width: 20vw; | |
| 1260 | min-height: 10vw; | |
| 1261 | background-color: #c3c3c3; | |
| 1262 | padding: 1vw 1vw 1vw 1vw; | |
| 1263 | border-radius: 1vw; | |
| 1264 | left: calc(50vw - 10vw); | |
| 1265 | position: absolute; | |
| 1266 | } | |
| 1267 | .confirmation_button_container{ | |
| 1268 | display: flex; | |
| 1269 | flex-direction: row; | |
| 1270 | width: 100%; | |
| 1271 | height: 15%; | |
| 1272 | justify-content: space-between; | |
| 1273 | align-items: center; | |
| 1274 | } | |
| 1275 | .confirmation_button_container button{ | |
| 1276 | width: 75px; | |
| 1277 | } | |
| 1278 | .context-menu{ | |
| 1279 | z-index: 10000; | |
| 1280 | position: fixed; | |
| 1281 | background: #c3c3c3; | |
| 1282 | color: #555; | |
| 1283 | font-family: sans-serif; | |
| 1284 | font-size: 11px; | |
| 1285 | -webkit-transition: opacity .5s ease-in-out; | |
| 1286 | -moz-transition: opacity .5s ease-in-out; | |
| 1287 | -ms-transition: opacity .5s ease-in-out; | |
| 1288 | -o-transition: opacity .5s ease-in-out; | |
| 1289 | transition: opacity .5s ease-in-out; | |
| 1290 | -webkit-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); | |
| 1291 | -moz-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); | |
| 1292 | box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); | |
| 1293 | padding: 0px; | |
| 1294 | border: 1px solid #C6C6C6; | |
| 1295 | } | |
| 1296 | .context-menu a { | |
| 1297 | display: block; | |
| 1298 | color: #555; | |
| 1299 | text-decoration: none; | |
| 1300 | padding: 6px 8px 6px 30px; | |
| 1301 | width: 250px; | |
| 1302 | position: relative; | |
| 1303 | } | |
| 1304 | ||
| 1305 | .context-menu a span { | |
| 1306 | color: #BCB1B3; | |
| 1307 | float: right; | |
| 1308 | } | |
| 1309 | ||
| 1310 | .context-menu a:hover { | |
| 1311 | color: #fff; | |
| 1312 | background: #3879D9; | |
| 1313 | } | |
| 1314 | ||
| 1315 | .context-menu hr { | |
| 1316 | border: 1px solid #EBEBEB; | |
| 1317 | border-bottom: 0; | |
| 1318 | } | |
| 1319 | .grid_table{ | |
| 1320 | border: solid 1px #dddddd; | |
| 1321 | border-collapse: collapse; | |
| 1322 | padding: 2px 3px; | |
| 1323 | text-align: center; | |
| 1324 | table-layout: fixed; | |
| 1325 | width: 100%; | |
| 1326 | } | |
| 1327 | ||
| 1328 | .grid_table_header{ | |
| 1329 | padding: 5px; | |
| 1330 | font: 12px Arial; | |
| 1331 | font-weight: normal; | |
| 1332 | color: #E3E3E3; | |
| 1333 | background-color: #666666; | |
| 1334 | height: 35px; | |
| 1335 | border: solid 1px #dddddd; | |
| 1336 | border-collapse: collapse; | |
| 1337 | text-align: center; | |
| 1338 | } | |
| 1339 | .grid_table_header form{ | |
| 1340 | display: flex; | |
| 1341 | justify-content: flex-end; | |
| 1342 | } | |
| 1343 | .grid_table_header form div:not(.grid_menu):not(.ignore_overflow):not(.grid_menu_options):not(.suggestion-container) { | |
| 1344 | text-overflow: ellipsis; | |
| 1345 | overflow: hidden; | |
| 1346 | white-space: nowrap; | |
| 1347 | } | |
| 1348 | .excel_table_header{ | |
| 1349 | padding: 5px; | |
| 1350 | font: 12px Arial; | |
| 1351 | font-weight: normal; | |
| 1352 | color: #E3E3E3; | |
| 1353 | background-color: #217346; | |
| 1354 | height: 35px; | |
| 1355 | border: solid 1px #dddddd; | |
| 1356 | border-collapse: collapse; | |
| 1357 | text-align: center; | |
| 1358 | } | |
| 1359 | .excel_table_header form{ | |
| 1360 | display: flex; | |
| 1361 | justify-content: flex-end; | |
| 1362 | } | |
| 1363 | .excel_table_header form div:not(.grid_menu):not(.ignore_overflow):not(.grid_menu_options) { | |
| 1364 | text-overflow: ellipsis; | |
| 1365 | overflow: hidden; | |
| 1366 | white-space: nowrap; | |
| 1367 | } | |
| 1368 | .grid_table_data{ | |
| 1369 | ||
| 1370 | height: inherit; | |
| 1371 | font: var(--mod-text-size) Arial; | |
| 1372 | border: solid 1px #dddddd; | |
| 1373 | border-collapse: collapse; | |
| 1374 | padding: calc(var(--mod-text-size) / 4); | |
| 1375 | text-align: center; | |
| 1376 | } | |
| 1377 | .grid_table_data .cell-data-wrapper{ | |
| 1378 | text-overflow: ellipsis; | |
| 1379 | text-wrap: nowrap; | |
| 1380 | overflow: hidden; | |
| 1381 | width: 90%; | |
| 1382 | } | |
| 1383 | .grid_table_data :is(select,input){ | |
| 1384 | text-overflow: ellipsis; | |
| 1385 | text-wrap: nowrap; | |
| 1386 | overflow: hidden; | |
| 1387 | } | |
| 1388 | .grid_table_data textarea{ | |
| 1389 | overflow: hidden; | |
| 1390 | } | |
| 1391 | .grid_table_data .contentarea{ | |
| 1392 | overflow: hidden; | |
| 1393 | font-size: var(--mod-text-size); | |
| 1394 | ||
| 1395 | } | |
| 1396 | .grid_table_data .contentarea a:visited{ | |
| 1397 | color: #336699; | |
| 1398 | } | |
| 1399 | .grid_table_data .contentarea a{ | |
| 1400 | color: #336699; | |
| 1401 | } | |
| 1402 | .grid_table_data .contentarea:has(a){ | |
| 1403 | text-decoration: underline; | |
| 1404 | color: #336699; | |
| 1405 | -webkit-user-modify: read-write-plaintext-only; | |
| 1406 | overflow-wrap: break-word; | |
| 1407 | -webkit-line-break: after-white-space; | |
| 1408 | cursor: pointer; | |
| 1409 | } | |
| 1410 | .grid_table_data > * { | |
| 1411 | display: inline-block; | |
| 1412 | margin-left: 5px; | |
| 1413 | margin-right: 5px; | |
| 1414 | vertical-align: middle; | |
| 1415 | } | |
| 1416 | tr .grid_table_data > *:not(div) { | |
| 1417 | height: calc(var(--mod-text-size) + 4px); | |
| 1418 | display: inline-block; | |
| 1419 | font-size: var(--mod-text-size); | |
| 1420 | } | |
| 1421 | ||
| 1422 | .grid_menu{ | |
| 1423 | position: relative; | |
| 1424 | top: -1px; | |
| 1425 | left: -4px; | |
| 1426 | float: left; | |
| 1427 | padding: 0px 3px 0px 8px; | |
| 1428 | color: #CCCCCC; | |
| 1429 | z-index:1; | |
| 1430 | } | |
| 1431 | .grid_menu:hover{ | |
| 1432 | color: #FFFFFF; | |
| 1433 | } | |
| 1434 | .grid_menu_options{ | |
| 1435 | position:absolute; | |
| 1436 | width:calc(100% - 10px); | |
| 1437 | } | |
| 1438 | .grid_menu_options:hover{ | |
| 1439 | background-color: #D3D3D3; | |
| 1440 | } | |
| 1441 | .filter_box{ | |
| 1442 | position: relative; | |
| 1443 | width: 260px; | |
| 1444 | height: 220px; | |
| 1445 | border: 1px solid #888888; | |
| 1446 | background-color: #FFFFFF; | |
| 1447 | color: #000000; | |
| 1448 | padding: 5px; | |
| 1449 | text-align: left; | |
| 1450 | align: left; | |
| 1451 | float: left; | |
| 1452 | z-index: 3; | |
| 1453 | } | |
| 1454 | .pagination_container{ | |
| 1455 | margin-left: 5px; | |
| 1456 | margin-top: 5px; | |
| 1457 | margin-bottom: 5px; | |
| 1458 | border-top: 2px solid #666666; | |
| 1459 | } | |
| 1460 | .pagination_area{ | |
| 1461 | position: relative; | |
| 1462 | float: left; | |
| 1463 | font: 14px Arial; | |
| 1464 | color: #666666; | |
| 1465 | } | |
| 1466 | .pagination_links{ | |
| 1467 | position: relative; | |
| 1468 | top: 2px; | |
| 1469 | font: 14px Arial; | |
| 1470 | color: #666666; | |
| 1471 | text-decoration:none; | |
| 1472 | margin-left: 2px; | |
| 1473 | margin-right: 2px; | |
| 1474 | } | |
| 1475 | ||
| 1476 | .pagination_block{ | |
| 1477 | position: relative; | |
| 1478 | top : 2px; | |
| 1479 | border: 1px solid #666666; | |
| 1480 | padding: 0px 5px 0px 5px; | |
| 1481 | } | |
| 1482 | ||
| 1483 | .pagination_selected_link{ | |
| 1484 | font-weight: bold; | |
| 1485 | font: 20px Arial; | |
| 1486 | color: #444444; | |
| 1487 | /* text-decoration: underline; */ | |
| 1488 | } | |
| 1489 | ||
| 1490 | .pagination_page_number_textbox{ | |
| 1491 | position: relative; | |
| 1492 | width: 60px; | |
| 1493 | } | |
| 1494 | ||
| 1495 | .max_results_per_page{ | |
| 1496 | position: relative; | |
| 1497 | float: left; | |
| 1498 | top: 5px; | |
| 1499 | left: 5px; | |
| 1500 | font: 14px Arial; | |
| 1501 | color: #666666; | |
| 1502 | } | |
| 1503 | ||
| 1504 | .round{ | |
| 1505 | -webkit-border-radius: 3px; | |
| 1506 | -moz-border-radius: 3px; | |
| 1507 | border-radius: 3px; | |
| 1508 | } | |
| 1509 | ||
| 1510 | .grid_data_cells{ | |
| 1511 | height:18px; | |
| 1512 | position:relative; | |
| 1513 | font: 12px Arial; | |
| 1514 | font-weight: normal; | |
| 1515 | text-align:left; | |
| 1516 | padding-left: 5px; | |
| 1517 | } | |
| 1518 | ||
| 1519 | .grid_standard_td{ | |
| 1520 | color: #999999; | |
| 1521 | background-color:#FFFFFF; | |
| 1522 | } | |
| 1523 | .table th { | |
| 1524 | position: relative; | |
| 1525 | } | |
| 1526 | .resizer { | |
| 1527 | position: absolute; | |
| 1528 | top: 0; | |
| 1529 | right: 0; | |
| 1530 | width: 5px; | |
| 1531 | cursor: col-resize; | |
| 1532 | user-select: none; | |
| 1533 | height: 100%; | |
| 1534 | } | |
| 1535 | .resizer:hover, | |
| 1536 | ||
| 1537 | .resizing { | |
| 1538 | border-right: 2px solid #333333; | |
| 1539 | } | |
| 1540 | ||
| 1541 | .resizable { | |
| 1542 | border: 1px solid gray; | |
| 1543 | height: 100px; | |
| 1544 | width: 100px; | |
| 1545 | position: relative; | |
| 1546 | } | |
| 1547 | .cell_red{ | |
| 1548 | font: 12px Arial; | |
| 1549 | text-align: center; | |
| 1550 | color: #FF0000; | |
| 1551 | background-color:#CCCCCC; | |
| 1552 | } | |
| 1553 | .cell_red:hover{ | |
| 1554 | color: #FFFFFF; | |
| 1555 | background-color:#444444; | |
| 1556 | } | |
| 1557 | .cell_link{ | |
| 1558 | font: 12px Arial; | |
| 1559 | text-align: center; | |
| 1560 | text-decoration: underline; | |
| 1561 | color: #336699; | |
| 1562 | } | |
| 1563 | .cell_link:hover{ | |
| 1564 | font: 12px Arial; | |
| 1565 | text-align: center; | |
| 1566 | text-decoration: underline; | |
| 1567 | color: #000000; | |
| 1568 | } | |
| 1569 | .cell_green{ | |
| 1570 | font: 12px Arial; | |
| 1571 | text-align: center; | |
| 1572 | color: #666666; | |
| 1573 | background-color:#ccff99; | |
| 1574 | } | |
| 1575 | .cell_green:hover{ | |
| 1576 | color: #FFFFFF; | |
| 1577 | background-color:#444444; | |
| 1578 | } | |
| 1579 | .cell_yellow{ | |
| 1580 | font: 12px Arial; | |
| 1581 | font-weight: bold; | |
| 1582 | text-align: center; | |
| 1583 | color: #666666; | |
| 1584 | background-color:#ffffcc; | |
| 1585 | } | |
| 1586 | .start-hidden-row{ | |
| 1587 | display: none; | |
| 1588 | } | |
| 1589 | .end-hidden-row{ | |
| 1590 | display: none; | |
| 1591 | } | |
| 1592 | .suggestion-popup{ | |
| 1593 | width: 200px; | |
| 1594 | background-color: #FAFAFA; | |
| 1595 | padding: 10px 10px 10px 10px; | |
| 1596 | z-index: 999999; | |
| 1597 | position: fixed; | |
| 1598 | box-shadow: 1px 1px 1px rgba(0,0,0,0.8), inset 0px 1px 1px rgba(0, 0, 25, .3), inset -1px -1px 2px rgba(0, 0, 25, .3); | |
| 1599 | } | |
| 1600 | .grid_table_header .suggestion-popup .suggestion-container{ | |
| 1601 | overflow-y: scroll; | |
| 1602 | overflow-x: hidden; | |
| 1603 | max-height: 100px; | |
| 1604 | } | |
| 1605 | .suggestion-item{ | |
| 1606 | display: flex; | |
| 1607 | align-items: center; | |
| 1608 | justify-content: flex-start; | |
| 1609 | border-bottom: 1px dashed black; | |
| 1610 | } | |
| 1611 | .suggestion-item:hover{ | |
| 1612 | background-color: #a3a3a3; | |
| 1613 | } | |
| 1614 | .suggestion-title{ | |
| 1615 | margin-top: 0px; | |
| 1616 | margin-bottom: 0px; | |
| 1617 | display: flex; | |
| 1618 | align-items: center; | |
| 1619 | justify-content: center; | |
| 1620 | font-weight: bold; | |
| 1621 | } | |
| 1622 | .suggestion-link::before{ | |
| 1623 | content: "\\1f517"; | |
| 1624 | } | |
| 1625 | `; | |
| 1626 | } | |
| 1627 | ||
| 1628 | this.stylesheet.innerHTML += this.stylesheet_injection; | |
| 1629 | // Inserting the styles into the component. | |
| 1630 | this.shadowRoot.appendChild(this.stylesheet); | |
| 1631 | ||
| 1632 | this.grid_name = this.getAttribute("name"); | |
| 1633 | this.zoom_setting = this.reset_zoom_setting; | |
| 1634 | //First thing to do is set the ID of the grid component itself. | |
| 1635 | this.setAttribute('id', `grid-${this.grid_name}`); | |
| 1636 | ||
| 1637 | // Next create and insert the spinner | |
| 1638 | this.classList.add('shawn-grid-spinner'); | |
| 1639 | let load_spinner_wrapper = document.createElement('div'); | |
| 1640 | let load_spinner = document.createElement('div'); | |
| 1641 | let load_message = document.createElement('div'); | |
| 1642 | ||
| 1643 | load_spinner_wrapper.setAttribute('id', `grid-${this.grid_name}-spinner`); | |
| 1644 | load_spinner_wrapper.classList.add('spinner-container'); | |
| 1645 | load_spinner.classList.add(`spinner`); | |
| 1646 | load_message.classList.add(`load-message`); | |
| 1647 | load_message.innerHTML = "Loading..."; | |
| 1648 | ||
| 1649 | ||
| 1650 | load_spinner_wrapper.appendChild(load_spinner); | |
| 1651 | load_spinner_wrapper.appendChild(load_message); | |
| 1652 | this.shadowRoot.appendChild(load_spinner_wrapper); | |
| 1653 | ||
| 1654 | // Slowly being deprecated | |
| 1655 | this.grid_settings = [ | |
| 1656 | //How many results to show per page, this populates the dropdown below the grid | |
| 1657 | {id:`grid-${this.grid_name}`, show_result_options:"20,50,100"}, | |
| 1658 | //Max results to show per page, this is responsible for pagination links | |
| 1659 | {id:`grid-${this.grid_name}`, max_results:this.max_results}, | |
| 1660 | //Max pagination links to display on a page, this prevents you from having hundreds of pagination links of there are too many results returned | |
| 1661 | {id:`grid-${this.grid_name}`, max_pagination_links:"10"}, | |
| 1662 | ] | |
| 1663 | //Starting point of creating the grid. | |
| 1664 | //Async call to get the loaded filters, resolve the promise, and then another async fetch call to get the actual data. | |
| 1665 | ||
| 1666 | try{ | |
| 1667 | //retrieve the filters through the load_global_json function, then resolve the Fetch promise with .then() | |
| 1668 | //inside the .then(){}, we process the filters, then load the table display data using the same process | |
| 1669 | //finishing with calling create table now that we have all the data loaded. | |
| 1670 | // THIS REPLACED THE AJAX/DIV-STORAGE METHOD. | |
| 1671 | if(this.load_filters_url){ | |
| 1672 | this.load_global_json(this.load_filters_url).then(jsonData =>{ | |
| 1673 | // Escape the loaded json, making sure new line, carriage return, tab, etc are properly escaped in the json. | |
| 1674 | let filtered_filters = this.escape_json(jsonData); | |
| 1675 | this.GLOBAL_LOADED_FILTERS = filtered_filters; | |
| 1676 | ||
| 1677 | this.grid_filters = this.GLOBAL_LOADED_FILTERS; | |
| 1678 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 1679 | let filtered_data = this.escape_json(jsonData); | |
| 1680 | ||
| 1681 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 1682 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 1683 | if(this.load_target_page > 0){ | |
| 1684 | this.create_table(`grid-${this.grid_name}`, '', this.load_target_page); | |
| 1685 | }else{ | |
| 1686 | this.create_table(`grid-${this.grid_name}`, ''); | |
| 1687 | } | |
| 1688 | ||
| 1689 | }); | |
| 1690 | }); | |
| 1691 | }else{ | |
| 1692 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 1693 | let filtered_data = this.escape_json(jsonData); | |
| 1694 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 1695 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 1696 | this.create_table(`grid-${this.grid_name}`, ''); | |
| 1697 | ||
| 1698 | }); | |
| 1699 | } | |
| 1700 | } | |
| 1701 | catch(error){ | |
| 1702 | console.log(error); | |
| 1703 | } | |
| 1704 | ||
| 1705 | } | |
| 1706 | // Currently not serving a useful function. | |
| 1707 | disconnectedCallback(){ | |
| 1708 | console.log(this.default_styles); | |
| 1709 | console.log(this.default_widths); | |
| 1710 | console.log(this.grid_filters); | |
| 1711 | } | |
| 1712 | //Get a list of all filters to hide, then hide them | |
| 1713 | hide_filter_divs(grid_id){ | |
| 1714 | let grid_component; | |
| 1715 | if(this.nodeName != 'SHAWN-GRID'){ | |
| 1716 | grid_component = this.getRootNode(); | |
| 1717 | }else{ | |
| 1718 | grid_component = this; | |
| 1719 | } | |
| 1720 | ||
| 1721 | let elms = grid_component.shadowRoot.querySelectorAll(".filter_div"); | |
| 1722 | for(var i = 0; i < elms.length; i++){ | |
| 1723 | elms[i].style.display = 'none'; | |
| 1724 | } | |
| 1725 | } | |
| 1726 | create_table(grid_id, column_name, start_results_at){ | |
| 1727 | ||
| 1728 | //Reset ANY data changes | |
| 1729 | ||
| 1730 | this.modified_data = {}; | |
| 1731 | // Check Data verifies if data came in or not, if no data comes back it cleans up any existing table elements, creates | |
| 1732 | // a new element to display that there is no data, and returns true, | |
| 1733 | // which should then not generate a table, so return 0. | |
| 1734 | if(this.check_data()){ | |
| 1735 | return 0; | |
| 1736 | } | |
| 1737 | //Sets the values for our grid settings, such as max results, how many pagination links to show, how many rows to show, etc. | |
| 1738 | this.set_grid_settings(); | |
| 1739 | // Setting the pagination to the correct page, default is 1 | |
| 1740 | let current_page = 1; | |
| 1741 | //Grab the scroll offset of the existing table if it does exist. | |
| 1742 | let scroll_offset_x = 0; | |
| 1743 | let scroll_offset_y = 0; | |
| 1744 | ||
| 1745 | // If a previous grid existed, scroll to and refocus the cell they were previously working on. | |
| 1746 | let previous_grid_focus_array = this.get_previous_grid_scroll_focus(); | |
| 1747 | if(previous_grid_focus_array){ | |
| 1748 | current_page = previous_grid_focus_array[0]; | |
| 1749 | scroll_offset_x = previous_grid_focus_array[1]; | |
| 1750 | scroll_offset_y = previous_grid_focus_array[2]; | |
| 1751 | } | |
| 1752 | ||
| 1753 | // Check for distinct values in JSON results | |
| 1754 | // let distinct_values = {}; | |
| 1755 | // for(let j = 0; j < this.COLUMN_NAME_ORDER.length; j++){ | |
| 1756 | // // Extract all values based on the key name from column name order, build them into an array, then turn them into a set (guarantees uniques) | |
| 1757 | // let distinct_col_values = [...new Set(this.GLOBAL_JSON_RESULT.map(item => item[this.COLUMN_NAME_ORDER[j]]))]; | |
| 1758 | // distinct_values[this.COLUMN_NAME_ORDER[j]] = distinct_col_values; | |
| 1759 | // } | |
| 1760 | // console.log(distinct_values); | |
| 1761 | ||
| 1762 | ||
| 1763 | // Call the filter_json function and get back the filtered json_data to use in the rest of this create_table function that we are in | |
| 1764 | // Also saves the filtered data into the component variable GLOBAL_JSON_RESULT_FILTERED | |
| 1765 | let json_data = this.filter_json(grid_id, '', '', '', 'do_not_call_create_table'); | |
| 1766 | ||
| 1767 | // Primary key is required for more advanced functionality. | |
| 1768 | // console.log(json_data); | |
| 1769 | if(json_data.length > 0){ | |
| 1770 | if(!json_data[0][this.primary_key]){ | |
| 1771 | console.log("Primary Key Set Incorrectly") | |
| 1772 | } | |
| 1773 | } | |
| 1774 | ||
| 1775 | // Check Filtered Data checks the dataset after filtering it, if there's no data remaining, creates a message to explain that filters | |
| 1776 | // are preventing data from being displayed. | |
| 1777 | if(this.check_filtered_data(json_data)){ | |
| 1778 | return 0; | |
| 1779 | } | |
| 1780 | //-------------------------------------------------------------------------------- | |
| 1781 | if(!start_results_at){ | |
| 1782 | start_results_at = 1; | |
| 1783 | } | |
| 1784 | ||
| 1785 | //Create the dynamic table | |
| 1786 | let table = document.createElement("table"); | |
| 1787 | ||
| 1788 | //Add the table header classname | |
| 1789 | table.className = "grid_table table grid_tr_alternating"; | |
| 1790 | //Set ID attribute on element | |
| 1791 | table.setAttribute('id', grid_id+'_table'); | |
| 1792 | table.style.setProperty("--mod-text-size", `${this.zoom_setting}px`) | |
| 1793 | ||
| 1794 | //Add the THEAD for the resizing code to work | |
| 1795 | let thead = table.createTHead(); | |
| 1796 | if(this.configuration["super_header_information"]){ | |
| 1797 | thead.style.top = "35px"; | |
| 1798 | }else{ | |
| 1799 | thead.style.top = "0px"; | |
| 1800 | } | |
| 1801 | //Create the table header rows using the extracted header values from above | |
| 1802 | let tr = thead.insertRow(-1); //Table row | |
| 1803 | ||
| 1804 | //Row Number Header Implementation | |
| 1805 | let th_rowcount = document.createElement("th"); | |
| 1806 | th_rowcount.innerHTML = "#"; | |
| 1807 | th_rowcount.style.width = '25px'; | |
| 1808 | th_rowcount.classList.add("grid_table_header"); | |
| 1809 | tr.prepend(th_rowcount); | |
| 1810 | //End Row Number Header Implementation | |
| 1811 | for (let i = 0; i < this.COLUMN_NAME_ORDER.length; i++){ | |
| 1812 | // Extract all column values using the map function, then convert the result to a set to obtain the distinct values | |
| 1813 | // let distinct_col_values = [...new Set(this.GLOBAL_JSON_RESULT.map(item => item[this.COLUMN_NAME_ORDER[i]]))]; | |
| 1814 | // if(this.id == 'grid-az-1' && distinct_col_values.length <= 10){ | |
| 1815 | // console.log(`Valid Column: ${this.COLUMN_NAME_ORDER[i]}`) | |
| 1816 | // for(let j = 0; j < distinct_col_values.length; j++){ | |
| 1817 | // if(distinct_col_values[j]){ | |
| 1818 | // console.log(distinct_col_values[j]); | |
| 1819 | // } | |
| 1820 | // } | |
| 1821 | // } | |
| 1822 | let th = document.createElement("th");//Table header | |
| 1823 | th.title = this.COLUMN_NAME_ORDER[i]; | |
| 1824 | // IF-ELSE is checking for if you provided column renames or not. | |
| 1825 | let active_filter = ''; | |
| 1826 | if(this.grid_filter_columns.includes(this.COLUMN_NAME_ORDER[i])){ | |
| 1827 | active_filter = 'active_filter'; | |
| 1828 | } | |
| 1829 | ||
| 1830 | //Determine the column name | |
| 1831 | let col_name = ''; | |
| 1832 | if(this.configuration["column_information"][this.COLUMN_NAME_ORDER[i]]["rename"]){ | |
| 1833 | col_name = this.configuration["column_information"][this.COLUMN_NAME_ORDER[i]]["rename"]; | |
| 1834 | }else{ | |
| 1835 | col_name = this.COLUMN_NAME_ORDER[i]; | |
| 1836 | } | |
| 1837 | ||
| 1838 | // Creating a custom filter box if there was a custom_filter name passed in from the config | |
| 1839 | let custom_filter = ''; | |
| 1840 | custom_filter = this.column_information[this.COLUMN_NAME_ORDER[i]]["custom_filter"]; | |
| 1841 | let filter_menu = ''; | |
| 1842 | filter_menu = this.column_information[this.COLUMN_NAME_ORDER[i]]["filter_menu"]; | |
| 1843 | if(filter_menu == false){ | |
| 1844 | th.innerHTML = ` | |
| 1845 | ${col_name} | |
| 1846 | `; | |
| 1847 | }else if(custom_filter){ | |
| 1848 | th.innerHTML = ` | |
| 1849 | <form> | |
| 1850 | <div style="position:relative;width:100%;" onClick="sort_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', 'asc', ${start_results_at});">${col_name}</div> | |
| 1851 | <div class="grid_menu excel_menu ${active_filter}" | |
| 1852 | onClick=" | |
| 1853 | window.gridReference.get('${this.id}').hide_filter_divs('${this.id}'); | |
| 1854 | const sub_form = window.gridReference.get('${this.id}').shadowRoot.getElementById('${custom_filter}'); | |
| 1855 | const table_container = window.gridReference.get('${this.id}').shadowRoot.getElementById('${grid_id}_container'); | |
| 1856 | // sub_form.style.display='block'; | |
| 1857 | this.parentElement.style.justifyContent = ''; | |
| 1858 | let sub_form_x = sub_form.getBoundingClientRect()['x']; | |
| 1859 | let table_container_x = table_container.getBoundingClientRect()['x']; | |
| 1860 | if(sub_form_x < table_container_x){ | |
| 1861 | this.parentElement.style.justifyContent = 'flex-start'; | |
| 1862 | }else{ | |
| 1863 | this.parentElement.style.justifyContent = 'flex-end'; | |
| 1864 | } | |
| 1865 | ||
| 1866 | if(window.gridReference.get('${this.id}').shadowRoot.getElementById('${custom_filter}').style.display == 'none'){ | |
| 1867 | window.gridReference.get('${this.id}').shadowRoot.getElementById('${custom_filter}').style.display = 'block'; | |
| 1868 | }else{ | |
| 1869 | window.gridReference.get('${this.id}').shadowRoot.getElementById('${custom_filter}').style.display = 'none'; | |
| 1870 | } | |
| 1871 | " | |
| 1872 | >☰ </div> | |
| 1873 | </form> | |
| 1874 | ||
| 1875 | ||
| 1876 | ${this.column_information[this.COLUMN_NAME_ORDER[i]]["custom_filter_code"]} | |
| 1877 | `; | |
| 1878 | }else{ | |
| 1879 | th.innerHTML = ` | |
| 1880 | <form> | |
| 1881 | <div style="position:relative;width:100%;" onClick="sort_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', 'asc', ${start_results_at});">${col_name}</div> | |
| 1882 | <div class="grid_menu excel_menu ${active_filter}" onClick=" | |
| 1883 | let prev_status = window.gridReference.get('${this.id}').shadowRoot.getElementById('grid_element_${grid_id}_${i}').style.display; | |
| 1884 | window.gridReference.get('${this.id}').hide_filter_divs('${this.id}'); | |
| 1885 | if(prev_status == 'none'){ | |
| 1886 | console.log('NONE'); | |
| 1887 | //document.getElementById(grid_element_${grid_id}_${i}).style.display = 'block'; | |
| 1888 | window.gridReference.get('${this.id}').shadowRoot.getElementById('grid_element_${grid_id}_${i}').style.display = 'block'; | |
| 1889 | }else{ | |
| 1890 | //document.getElementById(grid_element_${grid_id}_${i}).style.display = 'none'; | |
| 1891 | window.gridReference.get('${this.id}').shadowRoot.getElementById('grid_element_${grid_id}_${i}').style.display = 'none'; | |
| 1892 | console.log('BLOCK'); | |
| 1893 | } | |
| 1894 | "> | |
| 1895 | <div>☰</div> | |
| 1896 | </div> | |
| 1897 | <div class="ignore_overflow filter_div" id="grid_element_${grid_id}_${i}" style="position:absolute;float:left;top:30px;left:90px;display:none;z-index:12000;"> | |
| 1898 | <div class="ignore_overflow" style="position:relative;float:left;margin:6px 5px 10px 5px;text-align:left;" class="page_font"> | |
| 1899 | ||
| 1900 | <div class="ignore_overflow" style="position:relative;width:320px;height:220px;float:left;left:-5px;top:-2px;padding:5px 5px 5px 5px;background-color:#D3D3D3;border:1px solid #666666;color:#444444;"> | |
| 1901 | ||
| 1902 | <div class="ignore_overflow" style="position:relative;float:left;top:3px;"> | |
| 1903 | ||
| 1904 | <div class="grid_menu_options ignore_overflow" style="position:relative;float:left;" onclick="window.gridReference.get('${this.id}').sort_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', 'asc', ${start_results_at});"> | |
| 1905 | <div style="position:absolute;transform: rotate(-90deg);left:2px;top:-2px;">></div> | |
| 1906 | <i style="position:relative;padding: 0px 0px 0px 13px;left:3px;">Sort Ascending</i> | |
| 1907 | </div> | |
| 1908 | ||
| 1909 | <div class="grid_menu_options ignore_overflow" style="position:relative;float:left;top:-14px;left:140px;" onclick="window.gridReference.get('${this.id}').sort_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', 'desc', ${start_results_at});"> | |
| 1910 | <div style="position:absolute;transform: rotate(90deg);left:162px;top:-2px;">></div> | |
| 1911 | <i style="position:relative;padding: 0px 0px 0px 13px;left:56px;">Sort Descending</i> | |
| 1912 | </div> | |
| 1913 | ||
| 1914 | </div> | |
| 1915 | ||
| 1916 | <br style="clear:both;"> | |
| 1917 | ||
| 1918 | <div class="ignore_overflow" style="position:relative;float:left;top:12px;"> | |
| 1919 | <div class="grid_menu_options grid_menu" style="position:relative;float:left;color:#444444;">Where | |
| 1920 | <select name="${grid_id}_col${i}_filter_type" id="${grid_id}_col${i}_filter_type" style="width:80px;" onKeyPress="if(event.keyCode === 13){window.gridReference.get('${this.id}').filter_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', '${grid_id}_col${i}_filter_type', '${grid_id}_col${i}_filter_value');}"> | |
| 1921 | <option value="like">contains</option> | |
| 1922 | <option value="=">=</option> | |
| 1923 | <option value="!=">!=</option> | |
| 1924 | <option value="<"><</option> | |
| 1925 | <option value=">">></option> | |
| 1926 | <option value="<="><=</option> | |
| 1927 | <option value=">=">>=</option> | |
| 1928 | <option value="!like">does not contain</option> | |
| 1929 | </select> | |
| 1930 | <input type="text" name="${grid_id}_col${i}_filter_value" id="${grid_id}_col${i}_filter_value" value="" style="width:170px;" autocomplete="off" onfocus=" | |
| 1931 | let grid = this.getRootNode().host; grid.get_distinct_filter_values(event, this); | |
| 1932 | " onkeypress=" | |
| 1933 | if(event.keyCode === 13){window.gridReference.get('${this.id}').filter_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', '${grid_id}_col${i}_filter_type', '${grid_id}_col${i}_filter_value');} | |
| 1934 | " onkeyup=" | |
| 1935 | let grid = this.getRootNode().host; | |
| 1936 | grid.get_distinct_filter_values(event, this, this.value); | |
| 1937 | "> | |
| 1938 | <br> | |
| 1939 | </div> | |
| 1940 | </div> | |
| 1941 | ||
| 1942 | ||
| 1943 | ||
| 1944 | <div class="ignore_overflow" style="position:relative;float:left;top:-6px;"> | |
| 1945 | <div class="grid_menu_options" style="position:relative;top:30px;left:90px;width:300px;"> | |
| 1946 | <input type="button" onclick="window.gridReference.get('${this.id}').filter_json('${grid_id}', '${this.COLUMN_NAME_ORDER[i]}', '${grid_id}_col${i}_filter_type', '${grid_id}_col${i}_filter_value');" value="Apply this filter"> | |
| 1947 | <input type="button" onclick="window.gridReference.get('${this.id}').clear_filters('${grid_id}');" value="Clear all filters"> | |
| 1948 | </div> | |
| 1949 | <hr style="position:relative;top:35px;"> | |
| 1950 | <br style="clear:both;"> | |
| 1951 | <div style="position:relative;top:24px;"><b>ACTIVE FILTERS</b><br>${this.grid_filter_list}</div><br> | |
| 1952 | </div> | |
| 1953 | ||
| 1954 | </div> | |
| 1955 | ||
| 1956 | </div> | |
| 1957 | </div> | |
| 1958 | </form> | |
| 1959 | `; | |
| 1960 | } | |
| 1961 | tr.appendChild(th); | |
| 1962 | //Add the table header classname | |
| 1963 | if(this.excel_edit){ | |
| 1964 | th.className = "excel_table_header"; | |
| 1965 | }else{ | |
| 1966 | th.className = "grid_table_header"; | |
| 1967 | } | |
| 1968 | //-------------------------------------------------------------------------------- | |
| 1969 | /*Set the default widths of the table headers*/ | |
| 1970 | //Reload any table widths that the user changed/resized -------------------------- | |
| 1971 | for (let indx = 0; indx < this.COLUMN_NAME_ORDER.length; indx++){ | |
| 1972 | if ((this.COLUMN_NAME_ORDER[indx] == this.COLUMN_NAME_ORDER[i]) && (this.configuration["column_information"][this.COLUMN_NAME_ORDER[indx]]["width"])){ | |
| 1973 | th.style.width = this.configuration["column_information"][this.COLUMN_NAME_ORDER[indx]]["width"]; | |
| 1974 | } | |
| 1975 | } | |
| 1976 | /*Any user modified table header widths will be applied here*/ | |
| 1977 | for (let gw of this.grid_widths) { | |
| 1978 | //If the id matches then this is the correct grid and column so we need to update the width | |
| 1979 | if(gw.id == `${grid_id}_column_${i}`){ | |
| 1980 | //Grid and column match found, override the TH width | |
| 1981 | th.style.width = gw.width; | |
| 1982 | } | |
| 1983 | } | |
| 1984 | } | |
| 1985 | ||
| 1986 | //Add the TBODY for the resizing code to work | |
| 1987 | let tbody = table.createTBody(); | |
| 1988 | let start_results_at_record = ((start_results_at - 1) * this.max_results); | |
| 1989 | ||
| 1990 | // Taking the styles provided by the config and converting them to a custom class and applying them to the element | |
| 1991 | // This prevents in-line styles from tampering with class styles. | |
| 1992 | this.COLUMN_NAME_ORDER.forEach((key) => { | |
| 1993 | let default_formatting = this.column_information[key]["style"]; | |
| 1994 | let class_name = `${key}_config_styles`; | |
| 1995 | let new_class = ``; | |
| 1996 | if(default_formatting.match(/^style\:/i)){ | |
| 1997 | //Remove the command link: from the data | |
| 1998 | default_formatting = default_formatting.replace(/(style\:\s{0,})/i, ""); | |
| 1999 | new_class += `.${class_name} { | |
| 2000 | ${default_formatting} | |
| 2001 | }`; | |
| 2002 | this.stylesheet.innerHTML += new_class; | |
| 2003 | } | |
| 2004 | }); | |
| 2005 | // Creating the initial rows and populating them | |
| 2006 | this.create_rows(start_results_at_record, json_data, tbody, this.max_results, "append"); | |
| 2007 | ||
| 2008 | // Make sure there is no existing table elements from a previous load. | |
| 2009 | this.delete_existing_table(); | |
| 2010 | ||
| 2011 | //Populate the new table into the div for display | |
| 2012 | let grid_container = this.shadowRoot; | |
| 2013 | // Create the container to insert the table into | |
| 2014 | let grid_wrapper = document.createElement("div"); | |
| 2015 | grid_wrapper.id = `${this.id}_container`; | |
| 2016 | grid_wrapper.classList.add('table_container'); | |
| 2017 | ||
| 2018 | grid_wrapper.appendChild(table); | |
| 2019 | grid_container.appendChild(grid_wrapper); | |
| 2020 | this.create_pagination_area(this.id, json_data, start_results_at); | |
| 2021 | ||
| 2022 | //Set the width of the bottom row below the dynamically created table so that the pagination links and the show results dropdown are the same width as the dynamically created table | |
| 2023 | ||
| 2024 | // Remove the loading spinner, table should be visible by now. | |
| 2025 | this.classList.remove('shawn-grid-spinner'); | |
| 2026 | if(this.shadowRoot.getElementById(`${this.id}-spinner`)){ | |
| 2027 | this.shadowRoot.getElementById(`${this.id}-spinner`).remove(); | |
| 2028 | } | |
| 2029 | //Attaching event listeners to the row elements. | |
| 2030 | //This has to happen last, otherwise while appending to innerHTML, we reparse the html and lose the event listeners. | |
| 2031 | //This was only working previously with setting the onclick or oncontext menu's with setAttribute, this way | |
| 2032 | //It will work with .onclick .oncontextmenu, or adding an event listener. | |
| 2033 | this.add_additional_features(grid_id); | |
| 2034 | this.load_resizing_code(`grid-${this.grid_name}`); | |
| 2035 | ||
| 2036 | // Rescrolling to where the grid was previously scrolled to | |
| 2037 | if(start_results_at == current_page){ | |
| 2038 | this.shadowRoot.querySelector('.table_container').scrollLeft = scroll_offset_x; | |
| 2039 | this.shadowRoot.querySelector('.table_container').scrollTop = scroll_offset_y; | |
| 2040 | } | |
| 2041 | ||
| 2042 | // This is to refocus on the last focused cell/input element. It is jarring if a reload happens midfocus. | |
| 2043 | if(this.LAST_ACTIVE_ELEMENT_ID){ | |
| 2044 | let last_active_element = this.shadowRoot.getElementById(this.LAST_ACTIVE_ELEMENT_ID); | |
| 2045 | if(last_active_element){ | |
| 2046 | let last_active_tag = last_active_element.tagName; | |
| 2047 | last_active_element.focus(); | |
| 2048 | if(last_active_tag == "INPUT" || last_active_tag == "TEXTAREA"){ | |
| 2049 | let set_selection_range = true; | |
| 2050 | if(last_active_tag == "INPUT"){ | |
| 2051 | let input_type = last_active_element.type; | |
| 2052 | if(input_type == 'checkbox'){ | |
| 2053 | set_selection_range = false; | |
| 2054 | } | |
| 2055 | } | |
| 2056 | if(set_selection_range){ | |
| 2057 | last_active_element.setSelectionRange(this.LAST_ACTIVE_ELEMENT_CURSOR_POSITION, this.LAST_ACTIVE_ELEMENT_CURSOR_POSITION); | |
| 2058 | } | |
| 2059 | } | |
| 2060 | } | |
| 2061 | this.LAST_ACTIVE_ELEMENT_CURSOR_POSITION = 0; | |
| 2062 | this.LAST_ACTIVE_ELEMENT_ID = ''; | |
| 2063 | } | |
| 2064 | document.body.style.cursor = 'default'; | |
| 2065 | // const final_table_container = this.shadowRoot.querySelector(`div#${this.id}_container`); | |
| 2066 | // Scroll Handler Feature, has not been moved to the add additional features section yet. | |
| 2067 | // TODO: | |
| 2068 | // final_table_container.addEventListener("scrollend", this.scroll_handler); | |
| 2069 | // final_table_container.addEventListener("wheel", this.scroll_handler); | |
| 2070 | ||
| 2071 | console.log("Table Created"); | |
| 2072 | ||
| 2073 | } | |
| 2074 | create_pagination_area(grid_id, json_data, start_results_at){ | |
| 2075 | //PAGINATION CODE /////////////////////////////////////////////////////////////////////////////////////////// | |
| 2076 | //Create the pagination links | |
| 2077 | let grid_container = window.gridReference.get(grid_id).shadowRoot; | |
| 2078 | let number_of_pagination_links = Math.ceil(json_data.length / this.max_results); | |
| 2079 | ||
| 2080 | //Start the bottom row div, must put the HTML into a variable because pushing it straight into innerHTML will cause the browser to close all of my tags before I get a chance to close them first | |
| 2081 | let bottom_div_html = ''; | |
| 2082 | bottom_div_html += `<div id="${grid_id}_bottom_row" class="pagination_container">`; | |
| 2083 | ||
| 2084 | //Text for the patination links | |
| 2085 | bottom_div_html += `<div class="pagination_area">Page: `; | |
| 2086 | ||
| 2087 | ||
| 2088 | if(number_of_pagination_links > 0){ | |
| 2089 | //-------------------------------------------------------------------------------- | |
| 2090 | //Get the starting pagination link number ---------------------------------------- | |
| 2091 | let modified_start_at = start_results_at; | |
| 2092 | ||
| 2093 | //Decide if we draw the left pagniation page number links | |
| 2094 | if( start_results_at > (Number(this.max_pagination_links) / 2)){ | |
| 2095 | modified_start_at = (start_results_at - (Number(this.max_pagination_links) / 2)); | |
| 2096 | }else if(start_results_at <= (Number(this.max_pagination_links) / 2)){ | |
| 2097 | modified_start_at = (start_results_at - (Number(this.max_pagination_links) / 2)); | |
| 2098 | if(modified_start_at < 1){ | |
| 2099 | modified_start_at = 1; | |
| 2100 | } | |
| 2101 | } | |
| 2102 | //-------------------------------------------------------------------------------- | |
| 2103 | //Get the ending pagination link number ------------------------------------------ | |
| 2104 | let stop_pagination_links_at = (Number(this.max_pagination_links) + Number(start_results_at)); | |
| 2105 | ||
| 2106 | //Decide if we draw the right pagination page number links | |
| 2107 | if( start_results_at > (Number(this.max_pagination_links) / 2)){ | |
| 2108 | stop_pagination_links_at = ((Number(this.max_pagination_links) / 2) + Number(start_results_at)); | |
| 2109 | }else if(start_results_at <= (Number(this.max_pagination_links) / 2)){ | |
| 2110 | stop_pagination_links_at = ((Number(this.max_pagination_links) / 2) + Number(start_results_at)); | |
| 2111 | stop_pagination_links_at = (stop_pagination_links_at - (start_results_at - (Number(this.max_pagination_links) / 2)) ); | |
| 2112 | stop_pagination_links_at++; | |
| 2113 | } | |
| 2114 | ||
| 2115 | //Add buttons that allow you to go prior the pagination links that you are currently able to view | |
| 2116 | if(modified_start_at > 1){ | |
| 2117 | //Only show these links if we aren't on the last page already | |
| 2118 | ||
| 2119 | //Get the next page to display for the arrows in the pagination section | |
| 2120 | let previous_page = (Number(start_results_at) - 1); | |
| 2121 | ||
| 2122 | let start_pagination_at = (start_results_at + number_of_pagination_links); | |
| 2123 | ||
| 2124 | //Last page of results | |
| 2125 | bottom_div_html += `<a href="javascript:document.getElementById('${this.id}').create_table('${grid_id}', '', 1);" class="pagination_links pagination_block" title="Go to the first page: 1">❘❬❬</a>`; | |
| 2126 | // | |
| 2127 | //Add one to move one pagination link past the currently displayed one | |
| 2128 | bottom_div_html += `<a href="javascript:document.getElementById('${this.id}').create_table('${grid_id}', '', ${previous_page});" class="pagination_links pagination_block" title="Go to the previous page: ${previous_page}">❬</a>`; | |
| 2129 | } | |
| 2130 | //-------------------------------------------------------------------------------- | |
| 2131 | //-------------------------------------------------------------------------------- | |
| 2132 | ||
| 2133 | for (let i=modified_start_at; i<=stop_pagination_links_at; i++){ | |
| 2134 | ||
| 2135 | //-------------------------------------------------------------------------------- | |
| 2136 | //Add buttons that allow you to go past the pagination links that you are currently able to view | |
| 2137 | if(i > (stop_pagination_links_at - 1)){ | |
| 2138 | //Only show these links if we aren't on the last page already | |
| 2139 | ||
| 2140 | //Get the next page to display for the arrows in the pagination section | |
| 2141 | let next_page = (Number(start_results_at) + 1); | |
| 2142 | ||
| 2143 | let start_pagination_at = (start_results_at + number_of_pagination_links); | |
| 2144 | ||
| 2145 | if(start_results_at < number_of_pagination_links){ | |
| 2146 | //Add one to move one pagination link past the currently displayed one | |
| 2147 | bottom_div_html += `<a href="javascript:document.getElementById('${this.id}').create_table('${grid_id}', '', ${next_page});" class="pagination_links pagination_block" title="Go to the next page: ${next_page}">❭</a>`; | |
| 2148 | //Last page of results | |
| 2149 | bottom_div_html += `<a href="javascript:document.getElementById('${this.id}').create_table('${grid_id}', '', ${number_of_pagination_links});" class="pagination_links pagination_block" title="Go to the last page: ${number_of_pagination_links}">❭❭❘</a>`; | |
| 2150 | } | |
| 2151 | ||
| 2152 | //We have hit the maximum number of pagination links to display for this grid so stop building them | |
| 2153 | break; | |
| 2154 | } | |
| 2155 | //-------------------------------------------------------------------------------- | |
| 2156 | //-------------------------------------------------------------------------------- | |
| 2157 | ||
| 2158 | ||
| 2159 | //-------------------------------------------------------------------------------- | |
| 2160 | //Create the normal pagination links --------------------------------------------- | |
| 2161 | //Subtract one for the page to show as we will multiply this times the total results per page to find out which records to display | |
| 2162 | if(start_results_at == i){ | |
| 2163 | //Current selected page found, highlight the pagination number | |
| 2164 | bottom_div_html += `<a href="javascript:document.getElementById('${this.id}').create_table('${grid_id}', '', ${i});" id="pagination_page_${i}" class="pagination_links pagination_selected_link" title="Go to page: ${i}">${i}</a>`; | |
| 2165 | }else{ | |
| 2166 | //Standard pagination link, not the currenlty selected one | |
| 2167 | bottom_div_html += `<a href="javascript:document.getElementById('${this.id}').create_table('${grid_id}', '', ${i});" id="pagination_page_${i}" class="pagination_links" title="Go to page: ${i}">${i}</a>`; | |
| 2168 | } | |
| 2169 | ||
| 2170 | //Don't draw more pagination links then we have pages for | |
| 2171 | if(i >= number_of_pagination_links){ | |
| 2172 | break; | |
| 2173 | } | |
| 2174 | //-------------------------------------------------------------------------------- | |
| 2175 | //-------------------------------------------------------------------------------- | |
| 2176 | ||
| 2177 | } | |
| 2178 | // let i = test | |
| 2179 | //-------------------------------------------------------------------------------- | |
| 2180 | //Provide a textbox for letting the user type in a specific page ----------------- | |
| 2181 | bottom_div_html += `Page: <input type="text" id="${grid_id}_typein_page_number" name="" value="${start_results_at}" onKeyUp="if(event.keyCode === 13 && this.value && this.value > 0 && this.value <= ${number_of_pagination_links}){window.gridReference.get('${this.id}').create_table('${grid_id}', '', window.gridReference.get('${this.id}').shadowRoot.querySelector('#${grid_id}_typein_page_number').value);}" class="pagination_page_number_textbox">`; | |
| 2182 | //-------------------------------------------------------------------------------- | |
| 2183 | //-------------------------------------------------------------------------------- | |
| 2184 | } | |
| 2185 | ||
| 2186 | bottom_div_html += '</div>'; //Close the div we opened to go around the pagination section | |
| 2187 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 2188 | ||
| 2189 | ||
| 2190 | //SHOW NUMBER OF RESULTS PER PAGE /////////////////////////////////////////////////////////////////////////// | |
| 2191 | //Check which option should be selected | |
| 2192 | // Deprecated to always show 50 rows by default, we have infinite scrolling and page hopping. | |
| 2193 | bottom_div_html += `<div class="max_results_per_page">Results per page: <span id="${grid_id}_max_results">${this.max_results}</span></div>`; | |
| 2194 | ///////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
| 2195 | ||
| 2196 | //Close the bottom row div | |
| 2197 | bottom_div_html += '</div>'; | |
| 2198 | let table_container = grid_container.getElementById(`${grid_id}_container`); | |
| 2199 | table_container.insertAdjacentHTML("afterend", bottom_div_html); | |
| 2200 | // grid_container.innerHTML += bottom_div_html; | |
| 2201 | } | |
| 2202 | create_rows(start_results_at_record, json_data, tbody, insert_amount, prepend_append){ | |
| 2203 | let grid_id = this.id; | |
| 2204 | let row_id = start_results_at_record; | |
| 2205 | let current_record_number = 0; | |
| 2206 | let insert_direction = -1; | |
| 2207 | json_data = this.GLOBAL_JSON_RESULT_FILTERED; | |
| 2208 | if(prepend_append == "prepend"){ | |
| 2209 | insert_direction = 0; | |
| 2210 | } | |
| 2211 | // This for loop needs I to increment or decrement, depending on if we're prepending or appending rows | |
| 2212 | // This has necessitated a weird loop control, where we just set I = the first record we're adding | |
| 2213 | // to the table, then based off of variable prepend_append, we check conditionals, json_data.length | |
| 2214 | // if we're appending rows (scrolling down), or comparing I to 0 (scrolling up). | |
| 2215 | // At the very end of the loop, you will see logic to increment or decrement I. | |
| 2216 | // and at the start of the loop, that's the conditional to break out of the loop. | |
| 2217 | // As well as some logic inside the loop structure has if-else controls based on prepend_append. | |
| 2218 | for (let i=start_results_at_record; ;){ | |
| 2219 | if(prepend_append == "append"){ | |
| 2220 | if(i >= json_data.length){ | |
| 2221 | break; | |
| 2222 | } | |
| 2223 | }else{ | |
| 2224 | if(i < 0){ | |
| 2225 | break; | |
| 2226 | } | |
| 2227 | } | |
| 2228 | let tr = tbody.insertRow(insert_direction); | |
| 2229 | tr.setAttribute("id", `row${row_id}`) | |
| 2230 | if(prepend_append == "append"){ | |
| 2231 | row_id++; | |
| 2232 | }else{ | |
| 2233 | row_id--; | |
| 2234 | } | |
| 2235 | // Row Number Body Implementation | |
| 2236 | ||
| 2237 | let row_number = tr.insertCell(-1); | |
| 2238 | let unfiltered_row_index = -1; | |
| 2239 | for(let k = 0; k < this.GLOBAL_JSON_RESULT.length; k++){ | |
| 2240 | let result_string = JSON.stringify(this.GLOBAL_JSON_RESULT[k]); | |
| 2241 | let json_data_string = JSON.stringify(json_data[i]); | |
| 2242 | if(result_string == json_data_string){ | |
| 2243 | unfiltered_row_index = k; | |
| 2244 | break; | |
| 2245 | } | |
| 2246 | } | |
| 2247 | row_number.innerHTML = unfiltered_row_index + 1; | |
| 2248 | row_number.classList.add("row_number"); | |
| 2249 | // End Row Number Body Implementation | |
| 2250 | //Setting row ID's to remove/add back in rows; | |
| 2251 | ||
| 2252 | for (let j=0; j < this.COLUMN_NAME_ORDER.length; j++){ | |
| 2253 | // Testing Code | |
| 2254 | ||
| 2255 | // End Testing Code | |
| 2256 | let cell = tr.insertCell(-1); | |
| 2257 | // Potentially setting the title of the row to the ID of the grid will allow the in-JSON | |
| 2258 | // html to access the ID of the grid, by referring to its direct parents title. Potentially a way to allow multiples dynamically? | |
| 2259 | cell.setAttribute("title", `${this.id}`); | |
| 2260 | // Set the headers attribute so the columns know which header they belong to. This helps in the undo, delete, and copy functions. | |
| 2261 | cell.setAttribute("headers", this.COLUMN_NAME_ORDER[j]); | |
| 2262 | ||
| 2263 | // A-Z HARDCODED | |
| 2264 | const urlParams = new URLSearchParams(window.location.search); | |
| 2265 | const project_id = urlParams.get(this.url_identifier); | |
| 2266 | //I is the row index, J is the column index | |
| 2267 | cell.setAttribute("id", `${i},${j},${project_id},${json_data[i][this.primary_key]},${this.COLUMN_NAME_ORDER[j]}`) | |
| 2268 | // END A-Z HARDCODED | |
| 2269 | let td_data; | |
| 2270 | try{ | |
| 2271 | td_data = json_data[i][`${this.COLUMN_NAME_ORDER[j]}.html`]; | |
| 2272 | }catch(error){ | |
| 2273 | td_data = json_data[i][this.COLUMN_NAME_ORDER[j]]; | |
| 2274 | } | |
| 2275 | //Add the value/data to the TD tag | |
| 2276 | ||
| 2277 | // Adding the draggable arrow elements to each cell, for the copy-drag functionality to work. | |
| 2278 | ||
| 2279 | let cell_drag_element = document.createElement('div'); | |
| 2280 | cell_drag_element.classList.add('copy_drag'); | |
| 2281 | ||
| 2282 | let cell_drag_element_inner = document.createElement('div'); | |
| 2283 | cell_drag_element_inner.classList.add('copy_drag_inner'); | |
| 2284 | ||
| 2285 | ||
| 2286 | let resize_handle_element = document.createElement('div'); | |
| 2287 | resize_handle_element.classList.add('resize-handle'); | |
| 2288 | ||
| 2289 | /// ↓ is Unicode for down-arrow. | |
| 2290 | if(this.drag_increment_mode){ | |
| 2291 | cell_drag_element_inner.innerHTML = '+'; | |
| 2292 | }else{ | |
| 2293 | cell_drag_element_inner.innerHTML = '↓'; | |
| 2294 | } | |
| 2295 | cell_drag_element.appendChild(cell_drag_element_inner); | |
| 2296 | ||
| 2297 | if(!this.configuration["column_information"][this.COLUMN_NAME_ORDER[j]]["unlock"] && this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] != 'html'){ | |
| 2298 | let cell_data = json_data[i][this.COLUMN_NAME_ORDER[j]]; | |
| 2299 | if(cell_data == null){ | |
| 2300 | cell_data = ''; | |
| 2301 | } | |
| 2302 | // For text overflow purposes, we need to wrap raw text inputs with a div to overflow into ellipsis. | |
| 2303 | cell.innerHTML = `<div class="cell-data-wrapper">${cell_data}</div>`; | |
| 2304 | let reference_div = document.createElement('div'); | |
| 2305 | reference_div.value = cell_data; | |
| 2306 | reference_div.style.display = 'none'; | |
| 2307 | reference_div.setAttribute('id', `${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}`); | |
| 2308 | reference_div.setAttribute('reference-id', json_data[i][this.primary_key]); | |
| 2309 | ||
| 2310 | cell.appendChild(reference_div); | |
| 2311 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_html']){ | |
| 2312 | let additional_html = this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_html'].trim(); | |
| 2313 | let placeholder = document.createElement('div'); | |
| 2314 | placeholder.insertAdjacentHTML('afterbegin', additional_html); | |
| 2315 | let sub_elements = placeholder.querySelectorAll('*'); | |
| 2316 | for(let k = 0; k < sub_elements.length; k++){ | |
| 2317 | sub_elements[k].id += json_data[i][this.primary_key]; | |
| 2318 | } | |
| 2319 | cell.insertAdjacentHTML('beforeend', placeholder.innerHTML); | |
| 2320 | } | |
| 2321 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_javascript']){ | |
| 2322 | let additional_js = this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_javascript'].trim(); | |
| 2323 | let script_element = document.createElement('script'); | |
| 2324 | // Create the 3 default variables that let us access the correct item, grid_reference, cell_reference, inner_element | |
| 2325 | let wrapping_js = `if(true){let grid_reference = window.gridReference.get('${this.id}'); let inner_element = grid_reference.shadowRoot.getElementById('${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}'); let cell_reference = grid_reference.shadowRoot.getElementById('${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}').parentElement; `; | |
| 2326 | let closing_js = `}`; | |
| 2327 | // Insert the additional javascript after the initial variable creation | |
| 2328 | let complete_js = wrapping_js + additional_js + closing_js; | |
| 2329 | script_element.style.display = 'none'; | |
| 2330 | script_element.textContent = complete_js; | |
| 2331 | cell.appendChild(script_element); | |
| 2332 | } | |
| 2333 | if(json_data[i][`${this.COLUMN_NAME_ORDER[j]}.comment`]){ | |
| 2334 | cell.insertAdjacentHTML('afterbegin', json_data[i][`${this.COLUMN_NAME_ORDER[j]}.comment`]); | |
| 2335 | } | |
| 2336 | let listeners; | |
| 2337 | // Loop over the listeners and attach them to the field. | |
| 2338 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['listeners']){ | |
| 2339 | listeners = this.column_information[this.COLUMN_NAME_ORDER[j]]['listeners']; | |
| 2340 | Object.keys(listeners).forEach((key) => { | |
| 2341 | cell.setAttribute(key, listeners[key]); | |
| 2342 | }); | |
| 2343 | ||
| 2344 | } | |
| 2345 | // Checking whether to use config based html/javascript, or have it coming from the backend. | |
| 2346 | }else if(this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] && (this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] != 'html')){ | |
| 2347 | let inner_element; | |
| 2348 | let cell_data = json_data[i][this.COLUMN_NAME_ORDER[j]]; | |
| 2349 | if(cell_data == null){ | |
| 2350 | cell_data = ''; | |
| 2351 | } | |
| 2352 | // Content Area | |
| 2353 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] == 'contentarea'){ | |
| 2354 | inner_element = document.createElement('div'); | |
| 2355 | inner_element.insertAdjacentHTML('afterbegin', cell_data); | |
| 2356 | inner_element.contentEditable = 'plaintext-only'; | |
| 2357 | inner_element.onclick = this.anchor_click_handler; | |
| 2358 | inner_element.classList.add('contentarea'); | |
| 2359 | // Text Area | |
| 2360 | }else if(this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] == 'textarea'){ | |
| 2361 | inner_element = document.createElement('textarea'); | |
| 2362 | inner_element.insertAdjacentHTML('afterbegin', cell_data); | |
| 2363 | // Input | |
| 2364 | }else if(this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] == 'input'){ | |
| 2365 | inner_element = document.createElement('input'); | |
| 2366 | inner_element.setAttribute('value', cell_data); | |
| 2367 | ||
| 2368 | if(Number(this.column_information[this.COLUMN_NAME_ORDER[j]]['maxlength']) > 0){ | |
| 2369 | inner_element.setAttribute('maxlength', Number(this.column_information[this.COLUMN_NAME_ORDER[j]]['maxlength'])); | |
| 2370 | } | |
| 2371 | ||
| 2372 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['disable_field'] == true){ | |
| 2373 | inner_element.setAttribute('disabled', true); | |
| 2374 | } | |
| 2375 | // Select | |
| 2376 | }else if(this.column_information[this.COLUMN_NAME_ORDER[j]]['type'] == 'select'){ | |
| 2377 | inner_element = document.createElement('select'); | |
| 2378 | let options = this.column_information[this.COLUMN_NAME_ORDER[j]]['options']; | |
| 2379 | // Loop over the options provided in the config, then if one matches the data, set that as the selected option. | |
| 2380 | Object.keys(options).forEach((key) => { | |
| 2381 | let option = document.createElement('option'); | |
| 2382 | option.value = key; | |
| 2383 | option.innerHTML = options[key]; | |
| 2384 | if(key == cell_data){ | |
| 2385 | option.setAttribute('selected', 'selected'); | |
| 2386 | } | |
| 2387 | inner_element.appendChild(option); | |
| 2388 | ||
| 2389 | }); | |
| 2390 | // Setting the value as well. | |
| 2391 | inner_element.value = cell_data; | |
| 2392 | } | |
| 2393 | // The reference ID is the primary key ID number, such as if your primary key was az_project_item_id, the reference id would just be 7 | |
| 2394 | // This is how we can dynamicize the additional content and attached listeners, with ID's without knowing them in the template. | |
| 2395 | inner_element.setAttribute('reference-id', json_data[i][this.primary_key]); | |
| 2396 | // The ID includes the field name AND the primary key value. such as hub_a_hub7 | |
| 2397 | inner_element.setAttribute('id', `${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}`); | |
| 2398 | // Names and ID's are shared, for different use cases | |
| 2399 | inner_element.setAttribute('name', `${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}`); | |
| 2400 | ||
| 2401 | let listeners; | |
| 2402 | // Loop over the listeners and attach them to the field. | |
| 2403 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['listeners']){ | |
| 2404 | listeners = this.column_information[this.COLUMN_NAME_ORDER[j]]['listeners']; | |
| 2405 | Object.keys(listeners).forEach((key) => { | |
| 2406 | inner_element.setAttribute(key, listeners[key]); | |
| 2407 | }); | |
| 2408 | ||
| 2409 | } | |
| 2410 | // Attach sub styles | |
| 2411 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['substyle']){ | |
| 2412 | inner_element.style.cssText = `${this.column_information[this.COLUMN_NAME_ORDER[j]]['substyle']}`; | |
| 2413 | } | |
| 2414 | // Attach sub classes | |
| 2415 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['subclasses']){ | |
| 2416 | let classes = this.column_information[this.COLUMN_NAME_ORDER[j]]['subclasses'].split(' '); | |
| 2417 | inner_element.classList.add(classes); | |
| 2418 | ||
| 2419 | } | |
| 2420 | // Append the initial field | |
| 2421 | cell.appendChild(inner_element); | |
| 2422 | // If the field is bringing additional html, such as helper tags, loop and attach them here. | |
| 2423 | // This has a unique way to handle the ids, the ids in the config should leave the number off, | |
| 2424 | // the number will get added here. | |
| 2425 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_html']){ | |
| 2426 | let additional_html = this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_html'].trim(); | |
| 2427 | let placeholder = document.createElement('div'); | |
| 2428 | placeholder.insertAdjacentHTML('afterbegin', additional_html); | |
| 2429 | let sub_elements = placeholder.querySelectorAll('*'); | |
| 2430 | for(let k = 0; k < sub_elements.length; k++){ | |
| 2431 | sub_elements[k].id += json_data[i][this.primary_key]; | |
| 2432 | } | |
| 2433 | cell.insertAdjacentHTML('beforeend', placeholder.innerHTML); | |
| 2434 | } | |
| 2435 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_javascript']){ | |
| 2436 | let additional_js = this.column_information[this.COLUMN_NAME_ORDER[j]]['additional_javascript'].trim(); | |
| 2437 | let script_element = document.createElement('script'); | |
| 2438 | // Create the 3 default variables that let us access the correct item, grid_reference, cell_reference, inner_element | |
| 2439 | let wrapping_js = `if(true){let grid_reference = window.gridReference.get('${this.id}'); let inner_element = grid_reference.shadowRoot.getElementById('${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}'); let cell_reference = grid_reference.shadowRoot.getElementById('${this.COLUMN_NAME_ORDER[j]}${json_data[i][this.primary_key]}').parentElement; `; | |
| 2440 | let closing_js = `}`; | |
| 2441 | // Insert the additional javascript after the initial variable creation | |
| 2442 | let complete_js = wrapping_js + additional_js + closing_js; | |
| 2443 | script_element.style.display = 'none'; | |
| 2444 | script_element.textContent = complete_js; | |
| 2445 | cell.appendChild(script_element); | |
| 2446 | } | |
| 2447 | // Inserting the comments. | |
| 2448 | if(json_data[i][`${this.COLUMN_NAME_ORDER[j]}.comment`]){ | |
| 2449 | cell.insertAdjacentHTML('afterbegin', json_data[i][`${this.COLUMN_NAME_ORDER[j]}.comment`]); | |
| 2450 | } | |
| 2451 | }else{ | |
| 2452 | // Insert the html, followed by any comments if available. | |
| 2453 | ||
| 2454 | cell.innerHTML = `<div>${td_data}</div>`; | |
| 2455 | if(json_data[i][`${this.COLUMN_NAME_ORDER[j]}.comment`]){ | |
| 2456 | cell.insertAdjacentHTML('afterbegin', json_data[i][`${this.COLUMN_NAME_ORDER[j]}.comment`]); | |
| 2457 | } | |
| 2458 | let listeners; | |
| 2459 | if(this.column_information[this.COLUMN_NAME_ORDER[j]]['listeners']){ | |
| 2460 | listeners = this.column_information[this.COLUMN_NAME_ORDER[j]]['listeners']; | |
| 2461 | Object.keys(listeners).forEach((key) => { | |
| 2462 | cell.setAttribute(key, listeners[key]); | |
| 2463 | }); | |
| 2464 | ||
| 2465 | } | |
| 2466 | } | |
| 2467 | // cell.innerHTML = `${td_data}`; | |
| 2468 | //Add the table data classname | |
| 2469 | cell.className = "grid_table_data"; | |
| 2470 | ||
| 2471 | ||
| 2472 | //-------------------------------------------------------------------------------- | |
| 2473 | //Apply defaults for the grid table cells ---------------------------------------- | |
| 2474 | try{ | |
| 2475 | this.COLUMN_NAME_ORDER.forEach((key) => { | |
| 2476 | if(this.COLUMN_NAME_ORDER[j] == key){ | |
| 2477 | let class_name = `${key}_config_styles`; | |
| 2478 | let new_class = ``; | |
| 2479 | cell.classList.add(class_name); | |
| 2480 | cell.style.setProperty('width', ''); | |
| 2481 | } | |
| 2482 | }); | |
| 2483 | }catch(error){ | |
| 2484 | //We don't want an errors if the custom styles don't exist, we just wanted to only apply them if they did exist | |
| 2485 | } | |
| 2486 | if(this.configuration["column_information"][this.COLUMN_NAME_ORDER[j]]["unlock"]){ | |
| 2487 | if(this.drag_copy_features != false){ | |
| 2488 | cell.appendChild(cell_drag_element); | |
| 2489 | } | |
| 2490 | } | |
| 2491 | cell.appendChild(resize_handle_element); | |
| 2492 | // cell.addEventListener("keydown", this.copy_handler); | |
| 2493 | // cell.addEventListener("paste", this.excel_edit); | |
| 2494 | } | |
| 2495 | ||
| 2496 | current_record_number++; //Add one to track how many records we have displayed | |
| 2497 | if(current_record_number >= insert_amount){ | |
| 2498 | //If we have hit our max number of records then stop displaying them but keep adding pagination links | |
| 2499 | break; | |
| 2500 | } | |
| 2501 | ||
| 2502 | if(prepend_append == "append"){ | |
| 2503 | i++; | |
| 2504 | }else{ | |
| 2505 | i--; | |
| 2506 | } | |
| 2507 | } | |
| 2508 | let final_table = tbody; | |
| 2509 | for(let element of final_table.children){ | |
| 2510 | // Handling the context menu, whether custom or default | |
| 2511 | if(this.contextmenu && !this.excel_edit){ | |
| 2512 | element.oncontextmenu = this.right_click_row; | |
| 2513 | }else if(this.excel_edit){ | |
| 2514 | for(let cell of element.children){ | |
| 2515 | cell.setAttribute("contenteditable", "true"); | |
| 2516 | cell.addEventListener('keydown', this.excel_keydown_handler); | |
| 2517 | cell.addEventListener('paste', this.copy_handler, true); | |
| 2518 | } | |
| 2519 | } | |
| 2520 | // Adding the event listeners for mouse up and mouse down to the down arrow element, to handle copy dragging. | |
| 2521 | let draggables = element.querySelectorAll('.copy_drag'); | |
| 2522 | for(let drag_element of draggables){ | |
| 2523 | drag_element.addEventListener('mousedown', this.cell_down_handler); | |
| 2524 | drag_element.parentElement.addEventListener('mouseup', this.cell_up_handler); | |
| 2525 | } | |
| 2526 | } | |
| 2527 | } | |
| 2528 | get_json_row(row_number){ | |
| 2529 | return this.GLOBAL_JSON_RESULT_FILTERED[row_number]; | |
| 2530 | } | |
| 2531 | ||
| 2532 | get_json_single(row_number, key_name){ | |
| 2533 | return this.GLOBAL_JSON_RESULT_FILTERED[row_number][key_name]; | |
| 2534 | } | |
| 2535 | get_row_from_cell(cell){ | |
| 2536 | if(this.shadowRoot.getElementById(`${cell.id}`)){ | |
| 2537 | let row_number = cell.id.split(',')[0]; | |
| 2538 | return this.GLOBAL_JSON_RESULT_FILTERED[row_number]; | |
| 2539 | }else{ | |
| 2540 | console.log("Warning: Cell is not an element in this grid."); | |
| 2541 | } | |
| 2542 | } | |
| 2543 | get_value_from_cell(cell){ | |
| 2544 | if(this.shadowRoot.getElementById(`${cell.id}`)){ | |
| 2545 | let row_number = cell.id.split(',')[0]; | |
| 2546 | let header = cell.headers; | |
| 2547 | return this.GLOBAL_JSON_RESULT_FILTERED[row_number][header]; | |
| 2548 | }else{ | |
| 2549 | console.log("Warning: Cell is not an element in this grid."); | |
| 2550 | } | |
| 2551 | } | |
| 2552 | set_value_from_cell(cell, value){ | |
| 2553 | if(this.shadowRoot.getElementById(`${cell.id}`)){ | |
| 2554 | let row_number = cell.id.split(',')[0]; | |
| 2555 | let header = cell.headers; | |
| 2556 | this.GLOBAL_JSON_RESULT_FILTERED[row_number][header] = value; | |
| 2557 | }else{ | |
| 2558 | console.log("Warning: Cell is not an element in this grid."); | |
| 2559 | } | |
| 2560 | } | |
| 2561 | // Check Data checks that we have valid json, if we don't, display an unable to load message. | |
| 2562 | // Also cleans up elements of a previous table if they exist. | |
| 2563 | check_data(){ | |
| 2564 | // If no data returned from the backend, remove the spinner and any old grid elements, and display a no data message. | |
| 2565 | // The else if is incase the grid gets reloaded, we need to remove the no data message. | |
| 2566 | if(this.GLOBAL_JSON_RESULT.length == 0){ | |
| 2567 | if(this.shadowRoot.querySelector('.empty-json-container')){ | |
| 2568 | this.shadowRoot.querySelector('.empty-json-container').remove(); | |
| 2569 | } | |
| 2570 | if(this.shadowRoot.querySelector('.spinner-container')){ | |
| 2571 | let spinner = this.shadowRoot.querySelector('.spinner-container'); | |
| 2572 | spinner.remove(); | |
| 2573 | } | |
| 2574 | if(this.shadowRoot.querySelector('.table_container')){ | |
| 2575 | this.shadowRoot.querySelector('.table_container').remove(); | |
| 2576 | } | |
| 2577 | if(this.shadowRoot.querySelector('.pagination_container')){ | |
| 2578 | this.shadowRoot.querySelector('.pagination_container').remove() | |
| 2579 | } | |
| 2580 | let empty_json_container = document.createElement('div'); | |
| 2581 | let empty_json_message = document.createElement('div'); | |
| 2582 | empty_json_container.classList.add('empty-json-container'); | |
| 2583 | empty_json_message.innerHTML = `Table Unable To Load. No Data Provided.` | |
| 2584 | empty_json_container.appendChild(empty_json_message); | |
| 2585 | this.shadowRoot.appendChild(empty_json_container); | |
| 2586 | return true; | |
| 2587 | }else if(this.shadowRoot.querySelector('.empty-json-container')){ | |
| 2588 | this.shadowRoot.querySelector('.empty-json-container').remove(); | |
| 2589 | return false; | |
| 2590 | } | |
| 2591 | return false; | |
| 2592 | } | |
| 2593 | delete_existing_table(){ | |
| 2594 | while(this.shadowRoot.children.length > 1){ | |
| 2595 | if(this.shadowRoot.children[0].tagName == 'STYLE'){ | |
| 2596 | this.shadowRoot.children[1].remove(); | |
| 2597 | }else{ | |
| 2598 | this.shadowRoot.children[0].remove(); | |
| 2599 | } | |
| 2600 | } | |
| 2601 | } | |
| 2602 | check_filtered_data(filtered_data){ | |
| 2603 | if(filtered_data.length == 0){ | |
| 2604 | if(this.shadowRoot.querySelector('.spinner-container')){ | |
| 2605 | let spinner = this.shadowRoot.querySelector('.spinner-container'); | |
| 2606 | spinner.remove(); | |
| 2607 | } | |
| 2608 | if(this.shadowRoot.querySelector('table')){ | |
| 2609 | console.log("Table Found"); | |
| 2610 | if(this.shadowRoot.querySelector('.no_data_container')){ | |
| 2611 | this.shadowRoot.querySelector('.no_data_container').remove(); | |
| 2612 | } | |
| 2613 | if(this.shadowRoot.querySelector('.table_container')){ | |
| 2614 | this.shadowRoot.querySelector('.table_container').remove(); | |
| 2615 | } | |
| 2616 | ||
| 2617 | if(this.shadowRoot.querySelector('.toolbar_container')){ | |
| 2618 | this.shadowRoot.querySelector('.toolbar_container').remove(); | |
| 2619 | } | |
| 2620 | ||
| 2621 | if(this.shadowRoot.querySelector('.pagination_container')){ | |
| 2622 | this.shadowRoot.querySelector('.pagination_container').remove(); | |
| 2623 | } | |
| 2624 | if(this.shadowRoot.querySelector('.pagination_container')){ | |
| 2625 | this.shadowRoot.querySelector('.pagination_container').remove(); | |
| 2626 | } | |
| 2627 | } | |
| 2628 | // Creating the div to convey there is no data with the active filters | |
| 2629 | let no_data_container = document.createElement("div"); | |
| 2630 | no_data_container.classList.add("no_data_container"); | |
| 2631 | let no_data_display = document.createElement("div"); | |
| 2632 | no_data_display.id = 'no-data-div'; | |
| 2633 | no_data_display.innerHTML = this.grid_filter_list; | |
| 2634 | let no_data_message = document.createElement("p"); | |
| 2635 | //TODO: If statement wrap around this to verify whether there's no actual data, or if there's no data because of filters | |
| 2636 | no_data_message.innerHTML = "Data cannot be displayed with these active filters. Remove filters from the list below." | |
| 2637 | no_data_container.appendChild(no_data_message); | |
| 2638 | no_data_container.appendChild(no_data_display); | |
| 2639 | this.shadowRoot.appendChild(no_data_container); | |
| 2640 | ||
| 2641 | ||
| 2642 | document.body.style.cursor = 'default'; | |
| 2643 | ||
| 2644 | // Don't try to load the rest of the grid, wasted effort. | |
| 2645 | return true; | |
| 2646 | } | |
| 2647 | return false; | |
| 2648 | } | |
| 2649 | get_previous_grid_scroll_focus(){ | |
| 2650 | // This is to refocus on the last focused cell/input element. This section is to prevent onBlur being called every reload. | |
| 2651 | // We had an infinite loop of blur to reload, click new cell, reload grid, lose onblur, refocus etc. This prevents that | |
| 2652 | // At the cost of not being up to date while editing the cell. | |
| 2653 | if(this.shadowRoot.activeElement){ | |
| 2654 | let tag_name = this.shadowRoot.activeElement.tagName; | |
| 2655 | //Store the ID so we can refocus it later. | |
| 2656 | this.LAST_ACTIVE_ELEMENT_ID = this.shadowRoot.activeElement.id; | |
| 2657 | //If the last focused element was an input field, set the cursor back to the right character. | |
| 2658 | if(tag_name == "SELECT" || tag_name == "INPUT" || tag_name == "TEXTAREA"){ | |
| 2659 | this.LAST_ACTIVE_ELEMENT_CURSOR_POSITION = this.shadowRoot.activeElement.selectionStart; | |
| 2660 | } | |
| 2661 | } | |
| 2662 | if(this.shadowRoot.querySelector('.table_container')){ | |
| 2663 | const current_table = this.shadowRoot.querySelector('.table_container'); | |
| 2664 | let scroll_offset_x = current_table.scrollLeft; | |
| 2665 | let scroll_offset_y = current_table.scrollTop; | |
| 2666 | let current_page = Number(this.shadowRoot.querySelector('.pagination_selected_link').innerHTML); | |
| 2667 | return [current_page, scroll_offset_x, scroll_offset_y]; | |
| 2668 | } | |
| 2669 | return false; | |
| 2670 | } | |
| 2671 | set_grid_settings(){ | |
| 2672 | //Extract the header values ------------------------------------------------------ | |
| 2673 | let col = []; | |
| 2674 | ||
| 2675 | this.column_information = this.configuration["column_information"]; | |
| 2676 | //Convert the string back into a variable and dereference the data | |
| 2677 | ||
| 2678 | Object.keys(this.column_information).forEach((key) => { | |
| 2679 | col.push(key); | |
| 2680 | }); | |
| 2681 | // Copy the array from col into the Global COLUMN_NAME_ORDER, for use throughout the grid. | |
| 2682 | this.COLUMN_NAME_ORDER = Array.from(col); | |
| 2683 | for (let gs of this.grid_settings) { | |
| 2684 | //Get the max pagination links to display per page | |
| 2685 | if(this.id == gs.id && gs.max_pagination_links){ | |
| 2686 | this.max_pagination_links = gs.max_pagination_links; | |
| 2687 | } | |
| 2688 | } | |
| 2689 | //Loop through the filters displaying the ones that have been applied to this grid | |
| 2690 | ||
| 2691 | ||
| 2692 | this.grid_filter_list = ''; | |
| 2693 | this.grid_filter_columns = []; | |
| 2694 | for(let gf of this.grid_filters) { | |
| 2695 | if(gf.grid_id == this.id){ | |
| 2696 | //Currently reaching out ot the dom to get the component, then calling its functions. | |
| 2697 | //Potentially updateable to pass a direct reference to this component. | |
| 2698 | // REMOVE THIS TO GO BACK TO NORMAL ----------------------------------------------------------------------------------------------------------------------- | |
| 2699 | // Some grid templates have differing values for key-value pairs, most are x:x but some are x:y, x being the actually | |
| 2700 | // we have to do some renaming for display purposes, displaying Y, but processing and using X for the backend. | |
| 2701 | let grid_filter_display_value = gf.filter_value; | |
| 2702 | if(gf.column_name){ | |
| 2703 | let grid_component = this; | |
| 2704 | let column_type = grid_component.column_information[gf.column_name]["type"]; | |
| 2705 | if(column_type == "select"){ | |
| 2706 | let option_mapping = grid_component.column_information[gf.column_name]["options"]; | |
| 2707 | let filter_value_key_map = option_mapping[gf.filter_value]; | |
| 2708 | grid_filter_display_value = filter_value_key_map; | |
| 2709 | ||
| 2710 | } | |
| 2711 | } | |
| 2712 | ||
| 2713 | // REMOVE THIS TO GO BACK TO NORMAL ----------------------------------------------------------------------------------------------------------------------- | |
| 2714 | ||
| 2715 | ||
| 2716 | this.grid_filter_list += `<a href="javascript:window.gridReference.get('${this.id}').remove_filter('${this.id}','${gf.column_name}','${gf.filter_type}','${gf.filter_value}');" title="Click to remove this filter" style="color:#24406d">remove</a> - Where ${gf.column_name} ${gf.filter_type} '${grid_filter_display_value}'<br>`; | |
| 2717 | this.grid_filter_columns.push(gf.column_name); | |
| 2718 | } | |
| 2719 | } | |
| 2720 | //If no filters currently applied then set it to no filters are currently selected | |
| 2721 | if(this.grid_filter_list == ''){ | |
| 2722 | this.grid_filter_list += "no filters are currently selected<br>"; | |
| 2723 | } | |
| 2724 | } | |
| 2725 | add_additional_features(grid_id){ | |
| 2726 | let final_table = this.shadowRoot.getElementById(`${grid_id}_table`).querySelector('tbody'); | |
| 2727 | ||
| 2728 | // Start in default mode, which gives the user our custom right click menu | |
| 2729 | // When the Excel button is hit, the value is flipped, the rows no longer use our right click menu | |
| 2730 | // And the user gets the default browser right click menu back, for pasting purposes. | |
| 2731 | for(let element of final_table.children){ | |
| 2732 | // Handling the context menu, whether custom or default | |
| 2733 | if(this.contextmenu && !this.excel_edit){ | |
| 2734 | element.oncontextmenu = this.right_click_row; | |
| 2735 | }else if(this.excel_edit){ | |
| 2736 | for(let cell of element.children){ | |
| 2737 | cell.setAttribute("contenteditable", "true"); | |
| 2738 | cell.addEventListener('keydown', this.excel_keydown_handler); | |
| 2739 | cell.addEventListener('paste', this.copy_handler, true); | |
| 2740 | } | |
| 2741 | } | |
| 2742 | // Adding the event listeners for mouse up and mouse down to the down arrow element, to handle copy dragging. | |
| 2743 | let draggables = element.querySelectorAll('.copy_drag'); | |
| 2744 | for(let drag_element of draggables){ | |
| 2745 | drag_element.addEventListener('mousedown', this.cell_down_handler); | |
| 2746 | drag_element.parentElement.addEventListener('mouseup', this.cell_up_handler); | |
| 2747 | } | |
| 2748 | } | |
| 2749 | // Setting up the merge columns/super headers, if provided in the config json | |
| 2750 | if(this.configuration["super_header_information"]){ | |
| 2751 | // Create the container div for the pseudo header elements | |
| 2752 | let thead = this.shadowRoot.querySelector('thead'); | |
| 2753 | thead.style.top ="35px"; | |
| 2754 | ||
| 2755 | let super_div_row = document.createElement("div"); | |
| 2756 | super_div_row.classList.add("super_header_row"); | |
| 2757 | let super_header_information = this.configuration["super_header_information"]; | |
| 2758 | let column_information = this.configuration["column_information"]; | |
| 2759 | // Column offset to iterate over all columns used, allows multiple headers. 0 means no row number column, 1 means row number column. | |
| 2760 | let offset = 1; | |
| 2761 | // The container div needs to have a starting with to properly fit the table. | |
| 2762 | let super_div_row_width = 0; | |
| 2763 | // Loop over the header keys, and grab the colspan value from them. | |
| 2764 | let sub_header_columns = this.shadowRoot.getElementById(`${grid_id}_table`).querySelector('thead').children[0]; | |
| 2765 | let super_header_number = 0; | |
| 2766 | // Loop over each key in the super header information section of the config | |
| 2767 | // and calculate how many columns they encompass using offsetWidth. | |
| 2768 | Object.keys(super_header_information).forEach((key) => { | |
| 2769 | let super_header = document.createElement("div"); | |
| 2770 | super_header.id = `super_${super_header_number}`; | |
| 2771 | // How many headers to span | |
| 2772 | let colspan = super_header_information[key]["colspan"]; | |
| 2773 | let offset_increase = 0; | |
| 2774 | let super_header_width = 0; | |
| 2775 | for(let i = 0; i < colspan; i++){ | |
| 2776 | let current_header = sub_header_columns.children[i+offset] | |
| 2777 | current_header.setAttribute("super-header-owner", `super_${super_header_number}`); | |
| 2778 | ||
| 2779 | let child_header_width = current_header.offsetWidth; | |
| 2780 | // Increment the super header's width to match its children. | |
| 2781 | super_header_width += child_header_width; | |
| 2782 | offset_increase++; | |
| 2783 | ||
| 2784 | } | |
| 2785 | ||
| 2786 | // Increment the container row to properly match the width of all super headers. | |
| 2787 | super_div_row_width += super_header_width; | |
| 2788 | offset += offset_increase; | |
| 2789 | super_header_number++; | |
| 2790 | ||
| 2791 | if(super_header_information[key]["style"]){ | |
| 2792 | super_header.setAttribute("style", super_header_information[key]["style"]); | |
| 2793 | } | |
| 2794 | ||
| 2795 | super_header.style.width = `${super_header_width}px`; | |
| 2796 | // Renaming the super headers to their rename value if provided. | |
| 2797 | if(!key.includes("spacer")){ | |
| 2798 | if(super_header_information[key]["rename"]){ | |
| 2799 | super_header.setAttribute("super-header-name", super_header_information[key]["rename"]); | |
| 2800 | }else{ | |
| 2801 | super_header.setAttribute("super-header-name", key); | |
| 2802 | } | |
| 2803 | } | |
| 2804 | super_header.classList.add("super_header"); | |
| 2805 | super_div_row.appendChild(super_header); | |
| 2806 | }); | |
| 2807 | // Set the final width of the entire super header row, which is the total width of all child super headers | |
| 2808 | super_div_row.style.width = `${super_div_row_width}px`; | |
| 2809 | let final_container = this.shadowRoot.getElementById(`${grid_id}_container`); | |
| 2810 | // Prepend to the top of the table. | |
| 2811 | final_container.prepend(super_div_row); | |
| 2812 | } | |
| 2813 | // Setting up the interactive toolbar for the top row. | |
| 2814 | if(this.toolbar_allowed){ | |
| 2815 | // Remove existing toolbar on table redraw if exists. | |
| 2816 | if(this.shadowRoot.querySelector('.toolbar_container')){ | |
| 2817 | this.shadowRoot.querySelector('.toolbar_container').remove(); | |
| 2818 | } | |
| 2819 | let toolbar_container = document.createElement("div"); | |
| 2820 | ||
| 2821 | ||
| 2822 | this.shadowRoot.prepend(toolbar_container); | |
| 2823 | toolbar_container.classList.add("toolbar_container"); | |
| 2824 | let grid_owner = this.id; | |
| 2825 | ||
| 2826 | let icon_tooltip_container = document.createElement("div"); | |
| 2827 | icon_tooltip_container.classList.add("icon_tooltip_container"); | |
| 2828 | ||
| 2829 | ||
| 2830 | // Building the Excel Button | |
| 2831 | let excel_button = document.createElement("img"); | |
| 2832 | excel_button.classList.add("grid-icon"); | |
| 2833 | excel_button.setAttribute("title", "Enter Excel-Paste Mode"); | |
| 2834 | excel_button.setAttribute("src", "/assets/images/Microsoft-Excel-Logo.png"); | |
| 2835 | excel_button.setAttribute("grid-owner", grid_owner); | |
| 2836 | ||
| 2837 | // Building the Undo Button | |
| 2838 | let undo_button = document.createElement("button"); | |
| 2839 | undo_button.innerHTML = "<span>↶</span> Undo" | |
| 2840 | undo_button.style.height = "30px"; | |
| 2841 | undo_button.style.width = "65px"; | |
| 2842 | undo_button.setAttribute("grid-owner", grid_owner); | |
| 2843 | ||
| 2844 | // Building the button container | |
| 2845 | let button_container = document.createElement("div"); | |
| 2846 | button_container.classList.add("icon_container"); | |
| 2847 | button_container.appendChild(excel_button); | |
| 2848 | button_container.appendChild(undo_button); | |
| 2849 | ||
| 2850 | let toolbar_tooltips = document.createElement("div"); | |
| 2851 | toolbar_tooltips.classList.add("tooltips"); | |
| 2852 | let tooltip_1 = document.createElement("p"); | |
| 2853 | ||
| 2854 | tooltip_1.innerHTML = 'Click the Excel icon to enable/disable pasting from Excel.'; | |
| 2855 | tooltip_1.classList.add("page_font"); | |
| 2856 | let tooltip_2 = document.createElement("p"); | |
| 2857 | tooltip_2.classList.add("page_font"); | |
| 2858 | tooltip_2.innerHTML = 'Click Undo to undo a paste command. Currently limited to a history of 1.'; | |
| 2859 | toolbar_tooltips.appendChild(tooltip_1); | |
| 2860 | toolbar_tooltips.appendChild(tooltip_2); | |
| 2861 | // ----------------------------------------------------------------------------------- | |
| 2862 | // Building the drag increment toggle feature | |
| 2863 | let drag_toggle_container = document.createElement("div"); | |
| 2864 | drag_toggle_container.classList.add("increment-toggle-container"); | |
| 2865 | let drag_toggle_button = document.createElement("button"); | |
| 2866 | drag_toggle_button.id = `${this.id}-increment-toggle`; | |
| 2867 | drag_toggle_button.setAttribute("grid-reference", this.id); | |
| 2868 | drag_toggle_button.classList.add("increment-button"); | |
| 2869 | if(this.drag_increment_mode){ | |
| 2870 | drag_toggle_button.classList.add('increment-toggle'); | |
| 2871 | } | |
| 2872 | drag_toggle_button.insertAdjacentHTML('afterbegin', "Enable Increment on Drag"); | |
| 2873 | ||
| 2874 | drag_toggle_container.appendChild(drag_toggle_button); | |
| 2875 | // ---------------------------------------------------------------------------------------- | |
| 2876 | // Building the zoom in/out feature (increments text size); | |
| 2877 | let zoom_button_container = document.createElement("div"); | |
| 2878 | zoom_button_container.classList.add("zoom-button-container"); | |
| 2879 | ||
| 2880 | let zoom_in_button = document.createElement("button"); | |
| 2881 | let zoom_out_button = document.createElement("button"); | |
| 2882 | let zoom_reset_button = document.createElement("button"); | |
| 2883 | ||
| 2884 | zoom_in_button.id = `${this.id}-zoom-in`; | |
| 2885 | zoom_in_button.classList.add("zoom-in-button"); | |
| 2886 | zoom_in_button.setAttribute("grid-reference", this.id); | |
| 2887 | zoom_in_button.insertAdjacentHTML("afterbegin", `<svg fill="#000000" width="15px" height="15px" viewBox="0 0 200 200"> | |
| 2888 | <path d="M126.5,84.25h-22v-22a10,10,0,0,0-20,0v22h-22a10,10,0,0,0,0,20h22v22a10,10,0,0,0,20,0v-22h22a10,10,0,0,0,0-20Z"/> | |
| 2889 | <path d="M154.5,140.75a77.3,77.3,0,0,0,16-47c.5-42.5-34-77-76.5-77a77,77,0,0,0,0,154,76.21,76.21,0,0,0,47-16l25.5,25.5c4,4,10,4,13.5,0a9.67,9.67,0,0,0,0-14Zm-60.5,10a57,57,0,1,1,57-57A57,57,0,0,1,94,150.75Z"/> | |
| 2890 | </svg>`); | |
| 2891 | ||
| 2892 | zoom_out_button.id = `${this.id}-zoom-out`; | |
| 2893 | zoom_out_button.classList.add("zoom-out-button"); | |
| 2894 | zoom_out_button.setAttribute("grid-reference", this.id); | |
| 2895 | zoom_out_button.insertAdjacentHTML("afterbegin", `<svg fill="#000000" width="15px" height="15px" viewBox="0 0 200 200"> | |
| 2896 | <path d="M154.5,140.75a77.3,77.3,0,0,0,16-47c.5-42.5-34-77-76.5-77a77,77,0,0,0,0,154,76.21,76.21,0,0,0,47-16l25.5,25.5c4,4,10,4,13.5,0a9.67,9.67,0,0,0,0-14Zm-60.5,10a57,57,0,1,1,57-57A57,57,0,0,1,94,150.75Z"/> | |
| 2897 | <path d="M126.5,84.25h-64a10,10,0,0,0,0,20h64a10,10,0,0,0,0-20Z"/> | |
| 2898 | </svg>`); | |
| 2899 | ||
| 2900 | zoom_reset_button.id = `${this.id}-zoom-reset`; | |
| 2901 | zoom_reset_button.classList.add("zoom-reset-button"); | |
| 2902 | zoom_reset_button.setAttribute("grid-reference", this.id); | |
| 2903 | zoom_reset_button.insertAdjacentHTML("afterbegin", "Reset Zoom"); | |
| 2904 | ||
| 2905 | //! Micah Edit for highlighted rows | |
| 2906 | // Building the checkbox for turning filter rows on and off. | |
| 2907 | let highlight_checkbox = document.createElement("input"); | |
| 2908 | highlight_checkbox.type = "checkbox"; | |
| 2909 | highlight_checkbox.id = "highlight_checkbox_id"; | |
| 2910 | highlight_checkbox.name = "highlight_checkbox_name"; | |
| 2911 | highlight_checkbox.title = "Toggle row highlighting on mouse over on or off."; | |
| 2912 | if(this.highlight_checked){ | |
| 2913 | highlight_checkbox.checked = true; // Optional: set initial checked state to true | |
| 2914 | }else{ | |
| 2915 | highlight_checkbox.checked = false; | |
| 2916 | } | |
| 2917 | //highlight_checkbox.setAttribute("onclick", "check_row_highlight()"); | |
| 2918 | // 2. Create the label element | |
| 2919 | let highlight_label = document.createElement('label'); | |
| 2920 | highlight_label.htmlFor = "highlight_checkbox_id"; | |
| 2921 | highlight_label.title = "Toggle row highlighting on mouse over on or off."; | |
| 2922 | highlight_label.appendChild(document.createTextNode("Highlight Row")); | |
| 2923 | ||
| 2924 | // 3. Create a container (e.g., a div) to hold both, if desired | |
| 2925 | let highlight_container = document.createElement('div'); | |
| 2926 | highlight_container.classList.add("highlight_section"); | |
| 2927 | highlight_container.appendChild(highlight_checkbox); | |
| 2928 | highlight_container.appendChild(highlight_label); | |
| 2929 | //! | |
| 2930 | ||
| 2931 | // Building the filter symbol. | |
| 2932 | let filter_notification_div = document.createElement("div"); | |
| 2933 | filter_notification_div.classList.add("filter_notification"); | |
| 2934 | ||
| 2935 | let filter_notification_dot = document.createElement("div"); | |
| 2936 | ||
| 2937 | let notification_message = document.createElement("p"); | |
| 2938 | if(this.grid_filter_columns.length < 1){ | |
| 2939 | notification_message.innerHTML = `No Active Filters`; | |
| 2940 | filter_notification_dot.classList.add("filter_dot_none"); | |
| 2941 | filter_notification_dot.innerHTML = `✗`; | |
| 2942 | filter_notification_div.appendChild(filter_notification_dot); | |
| 2943 | filter_notification_div.appendChild(notification_message); | |
| 2944 | }else{ | |
| 2945 | notification_message.innerHTML = `There are Active Filters `; | |
| 2946 | filter_notification_dot.classList.add("filter_dot_active"); | |
| 2947 | filter_notification_dot.innerHTML = `✓`; | |
| 2948 | filter_notification_div.appendChild(filter_notification_dot); | |
| 2949 | filter_notification_div.appendChild(notification_message); | |
| 2950 | } | |
| 2951 | ||
| 2952 | // Assign onclicks after building, or we lose the listener. | |
| 2953 | highlight_checkbox.onclick = this.check_row_highlight; | |
| 2954 | undo_button.onclick = this.undo_copy; | |
| 2955 | excel_button.onclick = this.excel_mode; | |
| 2956 | drag_toggle_button.onclick = this.enable_increment; | |
| 2957 | zoom_in_button.onclick = this.zoom_in; | |
| 2958 | zoom_out_button.onclick = this.zoom_out; | |
| 2959 | zoom_reset_button.onclick = this.zoom_reset; | |
| 2960 | zoom_button_container.appendChild(zoom_reset_button); | |
| 2961 | zoom_button_container.appendChild(zoom_out_button); | |
| 2962 | zoom_button_container.appendChild(zoom_in_button); | |
| 2963 | ||
| 2964 | icon_tooltip_container.appendChild(button_container); | |
| 2965 | icon_tooltip_container.appendChild(toolbar_tooltips); | |
| 2966 | if(this.excel_button){ | |
| 2967 | toolbar_container.appendChild(icon_tooltip_container); | |
| 2968 | } | |
| 2969 | if(this.increment_button){ | |
| 2970 | toolbar_container.appendChild(drag_toggle_container); | |
| 2971 | } | |
| 2972 | if(this.zoom_button){ | |
| 2973 | toolbar_container.appendChild(zoom_button_container); | |
| 2974 | } | |
| 2975 | //! Micah Edit | |
| 2976 | // Add the checkbox to turn highlighting on and off | |
| 2977 | if(this.highlight_checkbox){ | |
| 2978 | toolbar_container.appendChild(highlight_container); | |
| 2979 | } | |
| 2980 | if(this.filter_notification){ | |
| 2981 | toolbar_container.appendChild(filter_notification_div); | |
| 2982 | } | |
| 2983 | ||
| 2984 | } | |
| 2985 | if(this.scroll_allowed){ | |
| 2986 | const final_table_container = this.shadowRoot.querySelector(`div#${this.id}_container`); | |
| 2987 | // final_table_container.addEventListener("scrollend", this.scroll_handler); | |
| 2988 | final_table_container.addEventListener("wheel", this.scroll_handler); | |
| 2989 | } | |
| 2990 | } | |
| 2991 | scroll_handler(event){ | |
| 2992 | let grid_reference = window.gridReference.get(this.id.replace('_container', '')); | |
| 2993 | let scroll_direction; | |
| 2994 | if(!event.shiftKey){ | |
| 2995 | let scroll_bypass_down = false; | |
| 2996 | let scroll_bypass_up = false; | |
| 2997 | if(this.scrollHeight == this.clientHeight){ | |
| 2998 | if(event.deltaY > 0){ | |
| 2999 | scroll_bypass_down = true; | |
| 3000 | }else{ | |
| 3001 | scroll_bypass_up = true; | |
| 3002 | } | |
| 3003 | } | |
| 3004 | // if there is no scroll bar (we have too little rows, or screen is fully displayed, this will always run, unless you check for this.scrollHeight) | |
| 3005 | // Scroll bypasses are for when there's no scroll bar, but potentially more data, such as last page scroll backwards. | |
| 3006 | let scroll_position = this.scrollHeight - this.scrollTop - this.clientHeight + 1; | |
| 3007 | // if((0 < scroll_position && scroll_position < 20) || (scroll_bypass_down)){ | |
| 3008 | if(((0 < scroll_position && scroll_position < 20) && event.deltaY > 0) || (scroll_bypass_down)){ | |
| 3009 | scroll_direction = "down"; | |
| 3010 | let rows = Array.from(grid_reference.shadowRoot.querySelectorAll("tbody tr:not([class*='hidden-row'])")); | |
| 3011 | let next_row = Number(rows[rows.length - 1].id.replace("row",'')) + 1; | |
| 3012 | if(next_row < grid_reference.GLOBAL_JSON_RESULT_FILTERED.length){ | |
| 3013 | for(let i = 0; i < 2; i++){ | |
| 3014 | let row = rows.shift(); | |
| 3015 | row.classList.add('start-hidden-row'); | |
| 3016 | } | |
| 3017 | ||
| 3018 | let tbody = grid_reference.shadowRoot.querySelector("tbody"); | |
| 3019 | let end_hidden_rows = Array.from(grid_reference.shadowRoot.querySelectorAll("tr.end-hidden-row")); | |
| 3020 | let insert_count = 2; | |
| 3021 | if(end_hidden_rows.length >= insert_count){ | |
| 3022 | for(let i = 0; i < insert_count; i++){ | |
| 3023 | end_hidden_rows[i].classList.remove('end-hidden-row'); | |
| 3024 | ||
| 3025 | } | |
| 3026 | this.scrollTop = this.scrollHeight - 10; | |
| 3027 | }else{ | |
| 3028 | insert_count = insert_count - end_hidden_rows.length; | |
| 3029 | for(let i = 0; i < end_hidden_rows.length; i++){ | |
| 3030 | end_hidden_rows[i].classList.remove('end-hidden-row'); | |
| 3031 | } | |
| 3032 | grid_reference.create_rows(next_row, grid_reference.GLOBAL_JSON_RESULT_FILTERED, tbody, insert_count, "append"); | |
| 3033 | this.scrollTop = this.scrollHeight - 10; | |
| 3034 | } | |
| 3035 | } | |
| 3036 | ||
| 3037 | // }else if((this.scrollTop <= 10) || (scroll_bypass_up)){ | |
| 3038 | }else if(((this.scrollTop <= 10) && event.deltaY < 0) || (scroll_bypass_up)){ | |
| 3039 | scroll_direction = "up"; | |
| 3040 | let hidden_rows = Array.from(grid_reference.shadowRoot.querySelectorAll("tr.start-hidden-row")); | |
| 3041 | let rows = Array.from(grid_reference.shadowRoot.querySelectorAll("tbody tr:not([class*='hidden-row'])")); | |
| 3042 | let row_count = 2; | |
| 3043 | if(hidden_rows.length > 0){ | |
| 3044 | if(hidden_rows.length < row_count){ | |
| 3045 | row_count = hidden_rows.length; | |
| 3046 | } | |
| 3047 | for(let i = 0; i < row_count; i++){ | |
| 3048 | if(hidden_rows.length - i - 1 >= 0){ | |
| 3049 | hidden_rows[hidden_rows.length - i - 1].classList.remove('start-hidden-row'); | |
| 3050 | } | |
| 3051 | } | |
| 3052 | if(rows.length > 0){ | |
| 3053 | row_count = 2; | |
| 3054 | if(rows.length < row_count){ | |
| 3055 | row_count = rows.length; | |
| 3056 | } | |
| 3057 | for(let i = 0; i < row_count; i++){ | |
| 3058 | if(rows.length - i - 1 >= 0 && (this.scrollHeight != this.clientHeight)){ | |
| 3059 | rows[rows.length - i - 1].classList.add('end-hidden-row'); | |
| 3060 | } | |
| 3061 | } | |
| 3062 | } | |
| 3063 | }else if(Number(rows[0].id.replace("row", "")) - 1 >= 0){ | |
| 3064 | // We subtract 1 here because we're going backwards through the data not forwards. | |
| 3065 | rows = Array.from(grid_reference.shadowRoot.querySelectorAll("tbody tr:not([class*='hidden-row'])")); | |
| 3066 | for(let i = 0; i < row_count; i++){ | |
| 3067 | if(rows.length - i - 1 >= 0 && (this.scrollHeight != this.clientHeight)){ | |
| 3068 | rows[rows.length - i - 1].classList.add('end-hidden-row'); | |
| 3069 | } | |
| 3070 | } | |
| 3071 | let next_row = Number(rows[0].id.replace("row", "")) - 1; | |
| 3072 | let tbody = grid_reference.shadowRoot.querySelector("tbody"); | |
| 3073 | let insert_count = 2; | |
| 3074 | grid_reference.create_rows(next_row, grid_reference.GLOBAL_JSON_RESULT_FILTERED, tbody, insert_count, "prepend"); | |
| 3075 | ||
| 3076 | } | |
| 3077 | } | |
| 3078 | // Updating pagination after scrolling logic has completed. | |
| 3079 | // Re-query the rows to make sure the new NodeList is updated after the scroll logic has been applied | |
| 3080 | if(scroll_direction){ | |
| 3081 | let existing_rows = grid_reference.shadowRoot.querySelectorAll("tbody tr:not([class*='hidden-row'])"); | |
| 3082 | let first_element = grid_reference.shadowRoot.querySelectorAll("tbody tr:not([class*='hidden-row'])")[0]; | |
| 3083 | let last_element = existing_rows[existing_rows.length - 1]; | |
| 3084 | let first_row = Number(first_element.id.replace("row", "")); | |
| 3085 | let last_row = Number(last_element.id.replace("row", "")); | |
| 3086 | let results_per_page = grid_reference.max_results; | |
| 3087 | console.log(results_per_page); | |
| 3088 | let current_page; | |
| 3089 | if(scroll_direction == "up"){ | |
| 3090 | current_page = Math.floor(first_row/results_per_page) + 1; | |
| 3091 | }else if(scroll_direction == "down"){ | |
| 3092 | current_page = Math.floor(last_row/results_per_page) + 1; | |
| 3093 | } | |
| 3094 | if(current_page <= 0){ | |
| 3095 | current_page = 1; | |
| 3096 | } | |
| 3097 | let pagination_number; | |
| 3098 | if(grid_reference.shadowRoot.getElementById(`pagination_page_${current_page}`)){ | |
| 3099 | pagination_number = grid_reference.shadowRoot.getElementById(`pagination_page_${current_page}`); | |
| 3100 | let page_input_element = grid_reference.shadowRoot.getElementById(`${grid_reference.id}_typein_page_number`); | |
| 3101 | page_input_element.value = current_page; | |
| 3102 | grid_reference.shadowRoot.querySelector('.pagination_selected_link').classList.remove('pagination_selected_link'); | |
| 3103 | pagination_number.classList.add("pagination_selected_link"); | |
| 3104 | }else{ | |
| 3105 | let pagination_container = grid_reference.shadowRoot.getElementById(`${grid_reference.id}_bottom_row`); | |
| 3106 | pagination_container.remove(); | |
| 3107 | grid_reference.create_pagination_area(grid_reference.id, grid_reference.GLOBAL_JSON_RESULT_FILTERED, current_page); | |
| 3108 | } | |
| 3109 | } | |
| 3110 | } | |
| 3111 | } | |
| 3112 | zoom_reset(){ | |
| 3113 | let grid_reference = window.gridReference.get(this.getAttribute("grid-reference")); | |
| 3114 | let grid_table = grid_reference.shadowRoot.querySelector("table"); | |
| 3115 | grid_reference.zoom_setting = grid_reference.reset_zoom_setting; | |
| 3116 | grid_table.style.setProperty("--mod-text-size", `${grid_reference.zoom_setting}px`); | |
| 3117 | } | |
| 3118 | zoom_in(){ | |
| 3119 | let grid_reference = window.gridReference.get(this.getAttribute("grid-reference")); | |
| 3120 | let grid_table = grid_reference.shadowRoot.querySelector("table"); | |
| 3121 | let current_text_size = Number(getComputedStyle(grid_table).getPropertyValue('--mod-text-size').replace("px", "")); | |
| 3122 | let new_text_size = current_text_size + 2; | |
| 3123 | grid_reference.zoom_setting = new_text_size; | |
| 3124 | grid_table.style.setProperty("--mod-text-size", `${new_text_size}px`); | |
| 3125 | } | |
| 3126 | zoom_out(){ | |
| 3127 | let grid_reference = window.gridReference.get(this.getAttribute("grid-reference")); | |
| 3128 | let grid_table = grid_reference.shadowRoot.querySelector("table"); | |
| 3129 | let current_text_size = Number(getComputedStyle(grid_table).getPropertyValue('--mod-text-size').replace("px", "")); | |
| 3130 | let new_text_size = current_text_size - 2; | |
| 3131 | if(new_text_size <= 6){ | |
| 3132 | new_text_size = 6; | |
| 3133 | } | |
| 3134 | grid_reference.zoom_setting = new_text_size; | |
| 3135 | grid_table.style.setProperty("--mod-text-size", `${new_text_size}px`); | |
| 3136 | // Resizers maintain the height of the original table, they have to get updated here. | |
| 3137 | // let resizer_height = grid_table.getBoundingClientRect()["height"]; | |
| 3138 | // let resizers = Array.from(grid_reference.shadowRoot.querySelectorAll('.resizer')); | |
| 3139 | // for(let i = 0; i < resizers.length; i++){ | |
| 3140 | // resizers[i].style.height = `${resizer_height}px`; | |
| 3141 | // } | |
| 3142 | } | |
| 3143 | enable_increment(){ | |
| 3144 | // This refers to the button that called this function, NOT the web component. | |
| 3145 | let grid_reference = window.gridReference.get(`${this.getAttribute("grid-reference")}`); | |
| 3146 | let drag_elements = grid_reference.shadowRoot.querySelectorAll(".copy_drag_inner"); | |
| 3147 | if(this.classList.contains("increment-toggle")){ | |
| 3148 | this.classList.remove("increment-toggle"); | |
| 3149 | this.innerHTML = "Enable Increment on Drag"; | |
| 3150 | grid_reference.drag_increment_mode = !grid_reference.drag_increment_mode; | |
| 3151 | for(let i = 0; i < drag_elements.length; i++){ | |
| 3152 | let drag_element = drag_elements[i]; | |
| 3153 | drag_element.innerHTML = "↓"; | |
| 3154 | } | |
| 3155 | }else{ | |
| 3156 | this.classList.add("increment-toggle"); | |
| 3157 | this.innerHTML = "Disable Increment on Drag"; | |
| 3158 | grid_reference.drag_increment_mode = !grid_reference.drag_increment_mode; | |
| 3159 | for(let i = 0; i < drag_elements.length; i++){ | |
| 3160 | let drag_element = drag_elements[i]; | |
| 3161 | drag_element.innerHTML = "+"; | |
| 3162 | } | |
| 3163 | } | |
| 3164 | } | |
| 3165 | // Filter the loaded json from the back, sets component variable GLOBAL_JSON_RESULT_FILTERED, and returns a copy of the filtered set. | |
| 3166 | // Not to be called from the template | |
| 3167 | manual_filter_json(grid_id, column_name, filter_type, filter_value, do_not_call_create_table){ | |
| 3168 | let table_element = window.gridReference.get(this.id).shadowRoot; | |
| 3169 | document.body.style.cursor = "wait"; | |
| 3170 | /*Copy the JSON from the global object */ | |
| 3171 | let json_data = Array.from(this.GLOBAL_JSON_RESULT); | |
| 3172 | //If re-loading filters that were pre-set or saved from before then only apply it to the grid that matches the saved filter data/records | |
| 3173 | if(!do_not_call_create_table){ | |
| 3174 | try{ | |
| 3175 | //Push this new filter data into the filters JSON blob | |
| 3176 | this.grid_filters.push({grid_id:grid_id, column_name:column_name, filter_type:filter_type, filter_value:filter_value}); | |
| 3177 | //Call the save filters function so that we can save these filters | |
| 3178 | this.save_filters(grid_id); | |
| 3179 | }catch(error){ | |
| 3180 | console.log(error); | |
| 3181 | } | |
| 3182 | } | |
| 3183 | //Applying all filters that had been saved and were loaded back in | |
| 3184 | for (let gf of this.grid_filters) { | |
| 3185 | //Make sure we are applying filters to the correct grid, skip each filter that doesn't match the grid we are on | |
| 3186 | if(grid_id != gf.grid_id){ | |
| 3187 | continue; | |
| 3188 | } | |
| 3189 | ||
| 3190 | column_name = gf.column_name; | |
| 3191 | ||
| 3192 | filter_type = gf.filter_type; | |
| 3193 | ||
| 3194 | //Lowercase for better match abilities | |
| 3195 | filter_value = gf.filter_value.toLowerCase(); | |
| 3196 | ||
| 3197 | //Remove starting and trailing spaces | |
| 3198 | filter_value = filter_value.trim(); | |
| 3199 | for (let i=0; i<json_data.length; i++){ | |
| 3200 | if(!Object.keys(json_data[i]).includes(column_name)){ | |
| 3201 | continue; | |
| 3202 | } | |
| 3203 | //Lowercase for better match abilities | |
| 3204 | //Account for empty values | |
| 3205 | let column_data; | |
| 3206 | if(json_data[i][column_name]){ | |
| 3207 | column_data = json_data[i][column_name].toLowerCase(); | |
| 3208 | }else{ | |
| 3209 | column_data = ''; | |
| 3210 | } | |
| 3211 | //Remove starting and trailing spaces | |
| 3212 | column_data = column_data.trim(); | |
| 3213 | //Regex data to search for | |
| 3214 | let re = new RegExp(filter_value); | |
| 3215 | if(filter_type){ | |
| 3216 | if(filter_type == '=' && column_data == filter_value){ | |
| 3217 | //console.log('='); | |
| 3218 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3219 | ||
| 3220 | } | |
| 3221 | else if(filter_type == 'in' && ((Number(column_data) >= Number(filter_value.split(',')[0]) || (Number(column_data) <= Number(filter_value.split(',')[1]))))){ | |
| 3222 | //console.log('<'); | |
| 3223 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3224 | ||
| 3225 | } | |
| 3226 | else if(filter_type == 'notin' && ((Number(column_data) <= Number(filter_value.split(',')[0]) || (Number(column_data) >= Number(filter_value.split(',')[1]))))){ | |
| 3227 | ||
| 3228 | //console.log('<'); | |
| 3229 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3230 | ||
| 3231 | } | |
| 3232 | else if(filter_type == '<' && Number(column_data) < Number(filter_value)){ | |
| 3233 | //console.log('<'); | |
| 3234 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3235 | ||
| 3236 | }else if(filter_type == '>' && Number(column_data) > Number(filter_value)){ | |
| 3237 | //console.log('>'); | |
| 3238 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3239 | ||
| 3240 | }else if(filter_type == '<=' && Number(column_data) <= Number(filter_value)){ | |
| 3241 | //console.log('<='); | |
| 3242 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3243 | ||
| 3244 | }else if(filter_type == '>=' && Number(column_data) >= Number(filter_value)){ | |
| 3245 | //console.log('>='); | |
| 3246 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3247 | ||
| 3248 | }else if(filter_type == 'like' && re.test(column_data) == true){ | |
| 3249 | //console.log('like'); | |
| 3250 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3251 | ||
| 3252 | }else if(filter_type == '!like' && re.test(column_data) == false){ | |
| 3253 | //console.log('!like'); | |
| 3254 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3255 | ||
| 3256 | }else if(filter_type == '!=' && column_data != filter_value){ | |
| 3257 | //console.log('!='); | |
| 3258 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3259 | ||
| 3260 | }else{ | |
| 3261 | //Remove this row of data | |
| 3262 | //console.log('Removed row containing non match on' + column_name + ':(' + json_data[i][column_name][0] + ')'); | |
| 3263 | let removed_element = json_data.splice(i, 1); | |
| 3264 | i--; | |
| 3265 | } | |
| 3266 | } | |
| 3267 | } | |
| 3268 | } | |
| 3269 | // console.log(json_data); | |
| 3270 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(json_data); | |
| 3271 | //Re-build the table if we didn't specify not to, as long as this was called from someone clicking on filters it is ok to call create_table but not if it is in a loop being auto loaded from prior data, this would create endless loops | |
| 3272 | if(!do_not_call_create_table){ | |
| 3273 | this.create_table(grid_id, column_name, 0); | |
| 3274 | } | |
| 3275 | //Return the filtered out/cleaned json_data | |
| 3276 | ||
| 3277 | return(json_data); | |
| 3278 | } | |
| 3279 | filter_json(grid_id, column_name, filter_type, filter_value, do_not_call_create_table){ | |
| 3280 | let table_element = window.gridReference.get(this.id).shadowRoot; | |
| 3281 | ||
| 3282 | ||
| 3283 | ||
| 3284 | ||
| 3285 | document.body.style.cursor = "wait"; | |
| 3286 | /*Copy the JSON from the global object */ | |
| 3287 | let json_data = Array.from(this.GLOBAL_JSON_RESULT); | |
| 3288 | let filter_lookup = true; | |
| 3289 | if(filter_type === 'like'){ | |
| 3290 | filter_lookup = false; | |
| 3291 | } | |
| 3292 | //If re-loading filters that were pre-set or saved from before then only apply it to the grid that matches the saved filter data/records | |
| 3293 | if(!do_not_call_create_table){ | |
| 3294 | try{ | |
| 3295 | if(filter_lookup){ | |
| 3296 | filter_type = table_element.getElementById(filter_type).value; | |
| 3297 | filter_value = table_element.getElementById(filter_value).value; | |
| 3298 | // REMOVE THIS TO GO BACK TO NORMAL ----------------------------------------------------------------------------------------------------------------------- | |
| 3299 | // Some grid templates have differing values for key-value pairs, most are x:x but some are x:y, x being the actually | |
| 3300 | // we have to do some renaming for display purposes, displaying Y, but processing and using X for the backend. | |
| 3301 | if(column_name){ | |
| 3302 | let grid_component = table_element.host; | |
| 3303 | let column_type = grid_component.column_information[column_name]["type"]; | |
| 3304 | if(column_type == "select"){ | |
| 3305 | let option_mapping = grid_component.column_information[column_name]["options"]; | |
| 3306 | let filter_value_key_map = Object.keys(option_mapping).find(key => option_mapping[key] === filter_value); | |
| 3307 | filter_value = filter_value_key_map; | |
| 3308 | ||
| 3309 | } | |
| 3310 | } | |
| 3311 | // REMOVE THIS TO GO BACK TO NORMAL ----------------------------------------------------------------------------------------------------------------------- | |
| 3312 | } | |
| 3313 | //Push this new filter data into the filters JSON blob | |
| 3314 | this.grid_filters.push({grid_id:grid_id, column_name:column_name, filter_type:filter_type, filter_value:filter_value}); | |
| 3315 | ||
| 3316 | //Call the save filters function so that we can save these filters | |
| 3317 | this.save_filters(grid_id); | |
| 3318 | }catch(error){ | |
| 3319 | console.log(error); | |
| 3320 | } | |
| 3321 | } | |
| 3322 | //Applying all filters that had been saved and were loaded back in | |
| 3323 | for (let gf of this.grid_filters) { | |
| 3324 | //Make sure we are applying filters to the correct grid, skip each filter that doesn't match the grid we are on | |
| 3325 | if(grid_id != gf.grid_id){ | |
| 3326 | continue; | |
| 3327 | } | |
| 3328 | ||
| 3329 | column_name = gf.column_name; | |
| 3330 | ||
| 3331 | filter_type = gf.filter_type; | |
| 3332 | ||
| 3333 | //Lowercase for better match abilities | |
| 3334 | filter_value = gf.filter_value.toLowerCase(); | |
| 3335 | ||
| 3336 | //Remove starting and trailing spaces | |
| 3337 | filter_value = filter_value.trim(); | |
| 3338 | for (let i=0; i<json_data.length; i++){ | |
| 3339 | if(!Object.keys(json_data[i]).includes(column_name)){ | |
| 3340 | continue; | |
| 3341 | } | |
| 3342 | //Lowercase for better match abilities | |
| 3343 | //Account for empty values | |
| 3344 | let column_data; | |
| 3345 | if(json_data[i][column_name]){ | |
| 3346 | column_data = json_data[i][column_name].toLowerCase(); | |
| 3347 | if(column_data.includes('<a href=')){ | |
| 3348 | const regex = /contenteditable="plaintext-only"\>(.*)\<\/a\>/m; | |
| 3349 | let re_match = column_data.match(regex); | |
| 3350 | ||
| 3351 | // get the first match group, which is the capturing group of the anchor tag | |
| 3352 | // Some grids inject links into the html, and NOT into a content editable div, HANDLE IT | |
| 3353 | // If we have a match, it's a contenteditable field, if we don't, it's not. | |
| 3354 | if(re_match){ | |
| 3355 | column_data = re_match[1]; | |
| 3356 | } | |
| 3357 | } | |
| 3358 | }else{ | |
| 3359 | column_data = ''; | |
| 3360 | } | |
| 3361 | //Remove starting and trailing spaces | |
| 3362 | column_data = column_data.trim(); | |
| 3363 | //Regex data to search for | |
| 3364 | let re = new RegExp(filter_value); | |
| 3365 | if(filter_type){ | |
| 3366 | if(filter_type == '=' && column_data == filter_value){ | |
| 3367 | //console.log('='); | |
| 3368 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3369 | ||
| 3370 | } | |
| 3371 | else if(filter_type == 'in' && ((Number(column_data) >= Number(filter_value.split(',')[0]) || (Number(column_data) <= Number(filter_value.split(',')[1]))))){ | |
| 3372 | console.log("in"); | |
| 3373 | //console.log('<'); | |
| 3374 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3375 | ||
| 3376 | } | |
| 3377 | else if(filter_type == 'notin' && ((Number(column_data) <= Number(filter_value.split(',')[0]) || (Number(column_data) >= Number(filter_value.split(',')[1]))))){ | |
| 3378 | console.log("notin"); | |
| 3379 | console.log(Number(column_data)); | |
| 3380 | //console.log('<'); | |
| 3381 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3382 | ||
| 3383 | } | |
| 3384 | else if(filter_type == '<' && Number(column_data) < Number(filter_value)){ | |
| 3385 | //console.log('<'); | |
| 3386 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3387 | ||
| 3388 | }else if(filter_type == '>' && Number(column_data) > Number(filter_value)){ | |
| 3389 | //console.log('>'); | |
| 3390 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3391 | ||
| 3392 | }else if(filter_type == '<=' && Number(column_data) <= Number(filter_value)){ | |
| 3393 | //console.log('<='); | |
| 3394 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3395 | ||
| 3396 | }else if(filter_type == '>=' && Number(column_data) >= Number(filter_value)){ | |
| 3397 | //console.log('>='); | |
| 3398 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3399 | ||
| 3400 | }else if(filter_type == 'like' && re.test(column_data) == true){ | |
| 3401 | //console.log('like'); | |
| 3402 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3403 | ||
| 3404 | }else if(filter_type == '!like' && re.test(column_data) == false){ | |
| 3405 | //console.log('!like'); | |
| 3406 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3407 | ||
| 3408 | }else if(filter_type == '!=' && column_data != filter_value){ | |
| 3409 | //console.log('!='); | |
| 3410 | //console.log('MATCH FOUND:(' + column_data + ')'); | |
| 3411 | ||
| 3412 | }else{ | |
| 3413 | //Remove this row of data | |
| 3414 | //console.log('Removed row containing non match on' + column_name + ':(' + json_data[i][column_name][0] + ')'); | |
| 3415 | let removed_element = json_data.splice(i, 1); | |
| 3416 | i--; | |
| 3417 | } | |
| 3418 | } | |
| 3419 | } | |
| 3420 | } | |
| 3421 | //Re-build the table if we didn't specify not to, as long as this was called from someone clicking on filters it is ok to call create_table but not if it is in a loop being auto loaded from prior data, this would create endless loops | |
| 3422 | if(!do_not_call_create_table){ | |
| 3423 | this.create_table(grid_id, column_name, 0); | |
| 3424 | } | |
| 3425 | //Return the filtered out/cleaned json_data | |
| 3426 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(json_data); | |
| 3427 | return(json_data); | |
| 3428 | } | |
| 3429 | // Generate a list of quick select options for filtering, based on number of distinct values | |
| 3430 | get_distinct_filter_values(e, source, search_value=''){ | |
| 3431 | let grid_component = source.getRootNode().host; | |
| 3432 | let column_name = source.closest('th').title; | |
| 3433 | let column_type = grid_component.column_information[column_name]["type"]; | |
| 3434 | ||
| 3435 | // console.log(grid_component.column_information[this.COLUMN_NAME_ORDER[j]]['options']); | |
| 3436 | let filter_box = source.parentElement.parentElement; | |
| 3437 | let target_id = source.id; | |
| 3438 | let source_dimensions = source.getBoundingClientRect(); | |
| 3439 | let source_x = source_dimensions.x; | |
| 3440 | let source_y = source_dimensions.y; | |
| 3441 | // Below gets all distinct values from the json result. | |
| 3442 | // For this column, search through the array of key/value stores ([{'key': value, 'key2': value2}, {'key': value, 'key2': value2}]) | |
| 3443 | // Pull out all values related to this columns key, so arr[i][column_name], convert to Set, to get distinct values. | |
| 3444 | if(grid_component.shadowRoot.getElementById(`${grid_component.id}_${column_name}_filter_suggestion`)){ | |
| 3445 | grid_component.shadowRoot.getElementById(`${grid_component.id}_${column_name}_filter_suggestion`).remove(); | |
| 3446 | } | |
| 3447 | let distinct_col_values = [...new Set(grid_component.GLOBAL_JSON_RESULT.map(item => item[column_name]))]; | |
| 3448 | // REMOVE THIS TO GO BACK TO NORMAL ----------------------------------------------------------------------------------------------------------------------- | |
| 3449 | // Some grid templates have differing values for key-value pairs, most are x:x but some are x:y, x being the actually | |
| 3450 | // we have to do some renaming for display purposes, displaying Y, but processing and using X for the backend. | |
| 3451 | if(column_type == "select"){ | |
| 3452 | let option_mapping = grid_component.column_information[column_name]["options"]; | |
| 3453 | for(let i = 0; i < distinct_col_values.length; i++){ | |
| 3454 | distinct_col_values[i] = option_mapping[distinct_col_values[i]]; | |
| 3455 | } | |
| 3456 | ||
| 3457 | } | |
| 3458 | // REMOVE THIS TO GO BACK TO NORMAL ----------------------------------------------------------------------------------------------------------------------- | |
| 3459 | ||
| 3460 | // Remove any "falsy" values, IE NaN, '', null, undefined, etc. | |
| 3461 | distinct_col_values = distinct_col_values.filter(value=>value); | |
| 3462 | if(search_value){ | |
| 3463 | distinct_col_values = distinct_col_values.filter((value)=> {return value.toLowerCase().includes(search_value.toLowerCase());}); | |
| 3464 | } | |
| 3465 | ||
| 3466 | distinct_col_values.sort(); | |
| 3467 | if(!grid_component.shadowRoot.getElementById(`${grid_component.id}_${column_name}_filter_suggestion`)){ | |
| 3468 | // if(distinct_col_values.length <= 10 && !grid_component.shadowRoot.getElementById(`${grid_component.id}_${column_name}_filter_suggestion`)){ | |
| 3469 | let suggestion_popup = document.createElement('div'); | |
| 3470 | suggestion_popup.id = `${grid_component.id}_${column_name}_filter_suggestion`; | |
| 3471 | suggestion_popup.classList.add('suggestion-popup'); | |
| 3472 | suggestion_popup.style.top = `${source_y}px`; | |
| 3473 | suggestion_popup.style.left = `${source_x + 90}px`; | |
| 3474 | ||
| 3475 | let suggestion_title = document.createElement('p'); | |
| 3476 | suggestion_title.innerText = '-Unique Values-' | |
| 3477 | suggestion_title.classList.add('suggestion-title'); | |
| 3478 | ||
| 3479 | let suggestion_container = document.createElement('div'); | |
| 3480 | suggestion_container.classList.add('suggestion-container'); | |
| 3481 | ||
| 3482 | suggestion_popup.appendChild(suggestion_title); | |
| 3483 | suggestion_popup.appendChild(suggestion_container); | |
| 3484 | for(let i = 0; i < distinct_col_values.length; i++){ | |
| 3485 | if(distinct_col_values[i]){ | |
| 3486 | let suggestion = document.createElement('div'); | |
| 3487 | // Checking for hyperlinked values | |
| 3488 | if(distinct_col_values[i].includes('<a href="') || distinct_col_values[i].includes("<a href='")){ | |
| 3489 | // distinct_col_values[i].replace('<br>', ''); | |
| 3490 | let regex = /contenteditable="plaintext-only"\>(.*)\<\/a\>/m; | |
| 3491 | let re_match = distinct_col_values[i].match(regex); | |
| 3492 | // get the first match group, which is the capturing group of the anchor tag | |
| 3493 | if(!re_match){ | |
| 3494 | regex = /\>(.*)\<\/a\>/m; | |
| 3495 | re_match = distinct_col_values[i].match(regex); | |
| 3496 | } | |
| 3497 | suggestion.innerHTML = re_match[1]; | |
| 3498 | suggestion.classList.add('suggestion-link'); | |
| 3499 | suggestion.style.color = '#336699'; | |
| 3500 | // suggestion.innerText = ; | |
| 3501 | }else{ | |
| 3502 | suggestion.innerText = distinct_col_values[i]; | |
| 3503 | } | |
| 3504 | suggestion.onclick = (e) => { | |
| 3505 | source.value = e.target.innerText; | |
| 3506 | suggestion_popup.remove(); | |
| 3507 | } | |
| 3508 | suggestion.classList.add('suggestion-item'); | |
| 3509 | suggestion_container.appendChild(suggestion); | |
| 3510 | } | |
| 3511 | } | |
| 3512 | filter_box.appendChild(suggestion_popup); | |
| 3513 | } | |
| 3514 | } | |
| 3515 | // Increase or decrease the number of displayed rows. | |
| 3516 | // Not to be called from the template | |
| 3517 | // Deprecated Function | |
| 3518 | update_max_results(grid_id, column_name){ | |
| 3519 | document.body.style.cursor = "wait"; | |
| 3520 | //Get the current max rows to display for this particular grid | |
| 3521 | // let dropdown_value = this.shadowRoot.getElementById(`${grid_id}_max_results`).value; | |
| 3522 | ||
| 3523 | //Re-build the table | |
| 3524 | this.create_table(grid_id, column_name, 0); | |
| 3525 | } | |
| 3526 | // Remove filters from the grid, and clear them out from the backend | |
| 3527 | // Not to be called from the template | |
| 3528 | clear_filters(grid_id){ | |
| 3529 | document.body.style.cursor = "wait"; | |
| 3530 | //Loop through all of the filters, if we find a matching grid_id then wipe that tables filters | |
| 3531 | let row_num = 0; | |
| 3532 | for (let gf of this.grid_filters) { | |
| 3533 | //Compare the ids, then make sure that we only remove filters by making sure there is a column name next to the id | |
| 3534 | if(grid_id == gf.grid_id && gf.column_name){ | |
| 3535 | this.grid_filters.splice(row_num, 1); | |
| 3536 | ||
| 3537 | this.clear_filters(grid_id); | |
| 3538 | }else{ | |
| 3539 | //Only increment if not found so that we keep in sync with not incrementing when we delete rows | |
| 3540 | row_num++; | |
| 3541 | } | |
| 3542 | } | |
| 3543 | ||
| 3544 | //Call the save filters function so that we can save these filters using AJAX to a DB | |
| 3545 | ||
| 3546 | this.save_filters(grid_id, 'clear_filters'); | |
| 3547 | // Set GLOBAL_JSON_RESULT_FILTERED back to the original unfiltered data. | |
| 3548 | ||
| 3549 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 3550 | ||
| 3551 | //The empty quotes are the column name to sort on, it is blank by default but if you always want to sort by default on a column then add it into these quotes | |
| 3552 | this.create_table(grid_id, '', 0); | |
| 3553 | } | |
| 3554 | // Remove individual filter and clear it out from the backend | |
| 3555 | // Not to be called from the template | |
| 3556 | remove_filter(grid_id, column_name, filter_type, filter_value){ | |
| 3557 | document.body.style.cursor = "wait"; | |
| 3558 | if(this.shadowRoot.querySelector('.no_data_container')){ | |
| 3559 | this.shadowRoot.querySelector('.no_data_container').remove(); | |
| 3560 | } | |
| 3561 | //Loop through all of the filters, if we find a matching grid_id then wipe that tables filters | |
| 3562 | let row_num = 0; | |
| 3563 | for (let gf of this.grid_filters) { | |
| 3564 | //Compare the ids, then make sure that we only remove filters by making sure there is a column name next to the id | |
| 3565 | if(grid_id == gf.grid_id && column_name == gf.column_name && filter_type == gf.filter_type && filter_value == gf.filter_value){ | |
| 3566 | this.grid_filters.splice(row_num, 1); | |
| 3567 | }else{ | |
| 3568 | //Only increment if not found so that we keep in sync with not incrementing when we delete rows | |
| 3569 | row_num++; | |
| 3570 | } | |
| 3571 | } | |
| 3572 | //Call the save filters function so that we can save these filters using AJAX to a DB | |
| 3573 | this.save_filters(grid_id, 'clear_filters'); | |
| 3574 | //The empty quotes are the column name to sort on, it is blank by default but if you always want to sort by default on a column then add it into these quotes | |
| 3575 | this.create_table(grid_id, '', 0); | |
| 3576 | } | |
| 3577 | // Save the active filters on the grid so they are active when the grid is reloaded for that user. | |
| 3578 | // Not to be called from the template | |
| 3579 | save_filters(grid_id, clear_filters){ | |
| 3580 | //Loop through all of the filters, if we find a matching grid_id then wipe that tables filters | |
| 3581 | let json_exists = 0; | |
| 3582 | let grid_json = ''; | |
| 3583 | grid_json += '['; | |
| 3584 | for (let gf of this.grid_filters) { | |
| 3585 | //Loop through the fiters and convert them to JSON for saving to the DB, ingore any that are not filters | |
| 3586 | if(gf.grid_id && gf.column_name){ | |
| 3587 | //Add a comma if this is not the first row | |
| 3588 | if(json_exists > 0){ | |
| 3589 | grid_json += ','; | |
| 3590 | } | |
| 3591 | ||
| 3592 | //Save the JSON for these | |
| 3593 | grid_json += `{"grid_id": "${gf.grid_id}", "column_name": "${gf.column_name}", "filter_type": "${gf.filter_type}", "filter_value": "${gf.filter_value}"}`; | |
| 3594 | ||
| 3595 | //Mark that we have created some JSON and it now exists | |
| 3596 | json_exists++; | |
| 3597 | }else{ | |
| 3598 | //Nothing matched, ignore | |
| 3599 | } | |
| 3600 | } | |
| 3601 | grid_json += ']'; | |
| 3602 | // Grid ID is always passed with the filters, even if they're empty. | |
| 3603 | //Call the URL to save the filters | |
| 3604 | try{ | |
| 3605 | //Check if there is any text other than square brackets | |
| 3606 | let matches = grid_json.match(/(\d|\w)+/i); | |
| 3607 | if(this.save_filters_url){ | |
| 3608 | if(matches){ | |
| 3609 | //Actual JSON found not just empty brackets, save this data | |
| 3610 | //console.log(grid_json); | |
| 3611 | //this.get_request(this.save_filters_url + encodeURIComponent(grid_json) + '&grid_id=' + grid_id + ).then(response=>{console.log(response)}); | |
| 3612 | let params = `&grid_id=${this.id}&filters=${encodeURIComponent(grid_json)}`; | |
| 3613 | this.get_request(this.save_filters_url + params).then(response=>{console.log(response)}); | |
| 3614 | }else if(clear_filters){ | |
| 3615 | //The clear filters function is what called this function so it is ok to update the filters without JSON in this instance | |
| 3616 | //console.log(grid_json); | |
| 3617 | let params = `&grid_id=${this.id}&filters=${encodeURIComponent(grid_json)}`; | |
| 3618 | this.get_request(this.save_filters_url + params).then(response=>{console.log(response)}); | |
| 3619 | }else{ | |
| 3620 | //No JSON found, do nothing | |
| 3621 | } | |
| 3622 | } | |
| 3623 | } | |
| 3624 | catch(error){ | |
| 3625 | console.log(error); | |
| 3626 | } | |
| 3627 | } | |
| 3628 | //! Micah Edit | |
| 3629 | check_row_highlight(e){ | |
| 3630 | console.log("run it") | |
| 3631 | //Call the URL to save the check preference | |
| 3632 | try{ | |
| 3633 | console.log(this); | |
| 3634 | let grid_root = this.getRootNode(); | |
| 3635 | let grid_component = grid_root.host; | |
| 3636 | console.log(grid_root); | |
| 3637 | console.log(grid_component); | |
| 3638 | console.log(grid_component.check_highlight_row_url); | |
| 3639 | // "this" reference in this case refers to the input element, not the grid. | |
| 3640 | // I should make that more clear and standardize the references to the grid_root/grid_component method. | |
| 3641 | // -Anthony | |
| 3642 | // if(this.check_highlight_row_url){ | |
| 3643 | // this.get_request(this.check_highlight_row_url).then(response=>{console.log(response)}); | |
| 3644 | // } | |
| 3645 | if(grid_component.check_highlight_row_url){ | |
| 3646 | console.log("On Click Succeeding to here"); | |
| 3647 | grid_component.get_request(grid_component.check_highlight_row_url).then(response=>{console.log(response); window.location.reload();}); | |
| 3648 | } | |
| 3649 | } | |
| 3650 | catch(error){ | |
| 3651 | console.log(error); | |
| 3652 | } | |
| 3653 | } | |
| 3654 | //! | |
| 3655 | ||
| 3656 | ||
| 3657 | // Creates the resizer divs that let the user change the width of the column after initial load. | |
| 3658 | // Does not get saved anywhere. | |
| 3659 | // Not to be called from the template | |
| 3660 | load_resizing_code(grid_id){ | |
| 3661 | //nested functions can't callback to the class "this", so we create a local reference to it | |
| 3662 | let this_reference = this; | |
| 3663 | const createResizableTable = function (table){ | |
| 3664 | const cols = table.querySelectorAll('th'); | |
| 3665 | let column_id =0; | |
| 3666 | [].forEach.call(cols, function (col){ | |
| 3667 | //It appears that without this added check, every mouse movement on the table creates hundreds of resizer divs | |
| 3668 | //It appears to be unnecessary to have more than 1 per column. | |
| 3669 | if(col.querySelectorAll(".resizer").length < 1){ | |
| 3670 | // Add a resizer element to the column | |
| 3671 | const resizer = document.createElement('div'); | |
| 3672 | resizer.classList.add('resizer'); | |
| 3673 | ||
| 3674 | //Set the height | |
| 3675 | // if(table.offsetHeight < 1){ | |
| 3676 | // let displayed_rows = Number(this_reference.shadowRoot.getElementById(`${this_reference.id}_max_results`).value); | |
| 3677 | // resizer.style.height = `${(displayed_rows * 35) + 45}px` | |
| 3678 | // }else{ | |
| 3679 | // let table_height = table.getBoundingClientRect()['height']; | |
| 3680 | // // resizer.style.height = `${table.offsetHeight}px`; | |
| 3681 | // resizer.style.height = `${table_height}px`; | |
| 3682 | ||
| 3683 | // } | |
| 3684 | ||
| 3685 | col.appendChild(resizer); | |
| 3686 | ||
| 3687 | createResizableColumn(col, resizer); | |
| 3688 | //Add an id to each column so that we can track which one we resized and save that data later if needed | |
| 3689 | col.setAttribute('id', `${grid_id}_column_${column_id}`); | |
| 3690 | ||
| 3691 | column_id++; | |
| 3692 | } | |
| 3693 | }); | |
| 3694 | }; | |
| 3695 | ||
| 3696 | const createResizableColumn = function (col, resizer){ | |
| 3697 | let x = 0; | |
| 3698 | let w = 0; | |
| 3699 | let previous_col_width = 0; | |
| 3700 | let super_header; | |
| 3701 | let super_header_row; | |
| 3702 | let row_old_width; | |
| 3703 | let header_old_width; | |
| 3704 | ||
| 3705 | ||
| 3706 | const mouseDownHandler = function (e){ | |
| 3707 | x = e.clientX; | |
| 3708 | if(col.getAttribute("super-header-owner")){ | |
| 3709 | super_header = this_reference.shadowRoot.getElementById(col.getAttribute("super-header-owner")); | |
| 3710 | super_header_row = super_header.parentElement; | |
| 3711 | row_old_width = Number(super_header_row.style.width.replace("px",'')); | |
| 3712 | header_old_width = Number(super_header.style.width.replace("px",'')); | |
| 3713 | // super_header_row.style.width = `${row_old_width + dx}px`; | |
| 3714 | // super_header.style.width = `${header_old_width + dx}px`; | |
| 3715 | } | |
| 3716 | let styles = window.getComputedStyle(col); | |
| 3717 | ||
| 3718 | w = parseInt(styles.width, 10); | |
| 3719 | ||
| 3720 | this_reference.shadowRoot.addEventListener('mousemove', mouseMoveHandler); | |
| 3721 | this_reference.shadowRoot.addEventListener('mouseup', mouseUpHandler); | |
| 3722 | ||
| 3723 | resizer.classList.add('resizing'); | |
| 3724 | }; | |
| 3725 | ||
| 3726 | const mouseMoveHandler = function (e){ | |
| 3727 | const dx = e.clientX - x; | |
| 3728 | let new_width = w+dx; | |
| 3729 | // Set the new width of the table header and table, as well as updating the json styles | |
| 3730 | if(this_reference.configuration["column_information"][col.title]["min_width"]){ | |
| 3731 | if((new_width) >= this_reference.configuration["column_information"][col.title]["min_width"]){ | |
| 3732 | if(col.getAttribute("super-header-owner")){ | |
| 3733 | super_header_row.style.width = `${row_old_width + dx}px`; | |
| 3734 | super_header.style.width = `${header_old_width + dx}px`; | |
| 3735 | } | |
| 3736 | col.style.width = `${new_width}px`; | |
| 3737 | } | |
| 3738 | }else{ | |
| 3739 | if((new_width) >= 10){ | |
| 3740 | if(col.getAttribute("super-header-owner")){ | |
| 3741 | super_header_row.style.width = `${row_old_width + dx}px`; | |
| 3742 | super_header.style.width = `${header_old_width + dx}px`; | |
| 3743 | } | |
| 3744 | col.style.width = `${new_width}px`; | |
| 3745 | } | |
| 3746 | } | |
| 3747 | ||
| 3748 | }; | |
| 3749 | ||
| 3750 | const mouseUpHandler = function (){ | |
| 3751 | previous_col_width = 0; | |
| 3752 | resizer.classList.remove('resizing'); | |
| 3753 | ||
| 3754 | this_reference.shadowRoot.removeEventListener('mousemove', mouseMoveHandler); | |
| 3755 | this_reference.shadowRoot.removeEventListener('mouseup', mouseUpHandler); | |
| 3756 | ||
| 3757 | //Save the width id and width object data ---------------------------------------- | |
| 3758 | const width_data = this_reference.grid_widths.find(data => data.id === col.id); | |
| 3759 | try{ | |
| 3760 | //Cause an error if the object has no data | |
| 3761 | if(width_data.id){ | |
| 3762 | console.log("FOUND: " + width_data.id); | |
| 3763 | } | |
| 3764 | ||
| 3765 | let found = 0; | |
| 3766 | for (let i of this_reference.grid_widths) { | |
| 3767 | if(i.id == col.id){ | |
| 3768 | //Update the existing record | |
| 3769 | i.width = col.style.width; | |
| 3770 | found = 1; | |
| 3771 | } | |
| 3772 | } | |
| 3773 | ||
| 3774 | //If not found then add a new record | |
| 3775 | if(found == 0){ | |
| 3776 | ||
| 3777 | this_reference.grid_widths.push({id:col.id, width:col.style.width}); | |
| 3778 | } | |
| 3779 | }catch(err){ | |
| 3780 | //The object had no data, save the data to the object | |
| 3781 | this_reference.grid_widths.push({id:col.id, width:col.style.width}); | |
| 3782 | } | |
| 3783 | }; | |
| 3784 | ||
| 3785 | resizer.addEventListener('mousedown', mouseDownHandler); | |
| 3786 | }; | |
| 3787 | createResizableTable(this_reference.shadowRoot.getElementById(`${grid_id}_table`)); | |
| 3788 | } | |
| 3789 | // Sort the json either ascending or descending, blank fields always come last in sort order, no matter the order | |
| 3790 | // Not to be called from the template | |
| 3791 | sort_json(grid_id, column_name, sort_direction, start_results_at){ | |
| 3792 | this.current_sorting = sort_direction; | |
| 3793 | document.body.style.cursor = "wait"; | |
| 3794 | //Retrieve the filtered json to be sorted. | |
| 3795 | let json_data = Array.from(this.GLOBAL_JSON_RESULT); | |
| 3796 | ||
| 3797 | let reA = /[^a-zA-Z]/g; | |
| 3798 | let reN = /[^0-9]/g; | |
| 3799 | json_data.sort( function( a, b ){ | |
| 3800 | // console.log(b); | |
| 3801 | // Error handling for NULL data | |
| 3802 | if(a[column_name]){ | |
| 3803 | a = a[column_name].toLowerCase(); | |
| 3804 | }else{ | |
| 3805 | a = "" | |
| 3806 | } | |
| 3807 | if(b[column_name]){ | |
| 3808 | b = b[column_name].toLowerCase(); | |
| 3809 | }else{ | |
| 3810 | b = "" | |
| 3811 | } | |
| 3812 | // This section is to handle always putting empty fields LAST. Invert the signs (negative/positive) on the 1's to invert the behavior. | |
| 3813 | if(sort_direction == 'asc'){ | |
| 3814 | if(a === ""){ | |
| 3815 | if(b === ""){ | |
| 3816 | return 0; | |
| 3817 | }else{ | |
| 3818 | return 1 | |
| 3819 | } | |
| 3820 | }else{ | |
| 3821 | if(b === ""){ | |
| 3822 | return -1; | |
| 3823 | } | |
| 3824 | } | |
| 3825 | }else{ | |
| 3826 | if(a === ""){ | |
| 3827 | if(b === ""){ | |
| 3828 | return 0; | |
| 3829 | }else{ | |
| 3830 | return -1; | |
| 3831 | } | |
| 3832 | }else{ | |
| 3833 | if(b === ""){ | |
| 3834 | return 1; | |
| 3835 | } | |
| 3836 | } | |
| 3837 | } | |
| 3838 | ||
| 3839 | let aA = a.replace(reA, ""); | |
| 3840 | let bA = b.replace(reA, ""); | |
| 3841 | if(aA === bA){ | |
| 3842 | let aN = parseFloat(a); | |
| 3843 | let bN = parseFloat(b); | |
| 3844 | return aN === bN ? 0 : aN > bN ? 1 : -1; | |
| 3845 | }else{ | |
| 3846 | return aA > bA ? 1 : -1; | |
| 3847 | } | |
| 3848 | }); | |
| 3849 | ||
| 3850 | if(sort_direction == 'desc'){ | |
| 3851 | json_data.reverse(); | |
| 3852 | } | |
| 3853 | //Reassign the json | |
| 3854 | this.GLOBAL_JSON_RESULT = Array.from(json_data); | |
| 3855 | //this.GLOBAL_JSON_RESULT_FILTERED = Array.from(json_data); | |
| 3856 | this.create_table(grid_id, column_name, start_results_at); | |
| 3857 | } | |
| 3858 | // Used by the context menu, this deletes 1 row from the backend and reloads the grid. | |
| 3859 | // Not to be called from the template | |
| 3860 | delete_row(id){ | |
| 3861 | //Get the index of the displayed results, based on the table row id (which is it's index in the displayed results, filtered or unfiltered). | |
| 3862 | let data_index = Number(id.replace("row", "")); | |
| 3863 | let removed_entry; | |
| 3864 | //If the results and filtered results are not the same length, we are actively filtering the data, and the row positions will be different. | |
| 3865 | if(this.GLOBAL_JSON_RESULT.length != this.GLOBAL_JSON_RESULT_FILTERED.length){ | |
| 3866 | //Using the index/rowID of the displayed data to remove it. Save the resulting array of json [{}] and retrieve the singular element | |
| 3867 | removed_entry = this.GLOBAL_JSON_RESULT_FILTERED.splice(data_index, 1); | |
| 3868 | //Get the index before removing it from the JSON | |
| 3869 | this.recent_deletion_index = this.GLOBAL_JSON_RESULT.indexOf(removed_entry) | |
| 3870 | //Remove the element from the unfiltered JSON | |
| 3871 | this.GLOBAL_JSON_RESULT.splice(this.GLOBAL_JSON_RESULT.indexOf(removed_entry), 1); | |
| 3872 | ||
| 3873 | this.recent_deletion = removed_entry; | |
| 3874 | ||
| 3875 | this.recent_deletion_filtered_index = data_index; | |
| 3876 | }else{ | |
| 3877 | //Unfiltered results are being displayed, row positions will line up exactly. | |
| 3878 | removed_entry = this.GLOBAL_JSON_RESULT.splice(data_index, 1); | |
| 3879 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 3880 | ||
| 3881 | this.recent_deletion = removed_entry; | |
| 3882 | this.recent_deletion_index = data_index; | |
| 3883 | this.recent_deletion_filtered_index = -1; | |
| 3884 | } | |
| 3885 | this.modified_data["action"] = "delete"; | |
| 3886 | console.log(removed_entry); | |
| 3887 | // TODO: This seems like a temporary measure, but we're testing it. | |
| 3888 | this.modified_data["source_data"] = removed_entry[0]; | |
| 3889 | // Reload the table, to properly recreate pagination | |
| 3890 | try{ | |
| 3891 | console.log("Attempting to save data."); | |
| 3892 | this.save_data(`grid-${this.grid_name}`, Number(this.shadowRoot.querySelector('.pagination_selected_link').innerHTML), 'delete'); | |
| 3893 | }catch(error){ | |
| 3894 | console.log("Deleted data not saved."); | |
| 3895 | console.log(error); | |
| 3896 | } | |
| 3897 | this.undo_delete_check = true; | |
| 3898 | } | |
| 3899 | // Undo the most recently deleted row, DOES NOT INTERACT WITH THE UNDO BUTTON | |
| 3900 | // Not to be called from the template | |
| 3901 | undo_delete_row(pagination_number){ | |
| 3902 | // Actively filtering the data, recover the deleted row into the filtered dataset and display table. | |
| 3903 | // Are we looking at filtered or unfiltered data | |
| 3904 | if(this.GLOBAL_JSON_RESULT_FILTERED.length != this.GLOBAL_JSON_RESULT){ | |
| 3905 | if(this.undo_delete_check){ | |
| 3906 | this.modified_data["action"] = 'undo'; | |
| 3907 | console.log(this.recent_deletion); | |
| 3908 | this.modified_data["source_data"] = this.recent_deletion[0]; | |
| 3909 | this.GLOBAL_JSON_RESULT_FILTERED.splice(this.recent_deletion_filtered_index, 0, this.recent_deletion); | |
| 3910 | this.GLOBAL_JSON_RESULT.splice(this.recent_deletion_index, 0, this.recent_deletion); | |
| 3911 | this.save_data(`grid-${this.grid_name}`, pagination_number, 'undo'); | |
| 3912 | }else{ | |
| 3913 | window.appToast && window.appToast({ message: 'Nothing to Undo. History only stores 1 record.', type: 'warning' }); | |
| 3914 | } | |
| 3915 | }else{ | |
| 3916 | if(this.undo_delete_check){ | |
| 3917 | this.modified_data["action"] = 'undo'; | |
| 3918 | console.log(this.recent_deletion); | |
| 3919 | this.modified_data["source_data"] = this.recent_deletion[0]; | |
| 3920 | this.GLOBAL_JSON_RESULT.splice(this.recent_deletion_index, 0, this.recent_deletion); | |
| 3921 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 3922 | this.save_data(`grid-${this.grid_name}`, pagination_number, 'undo'); | |
| 3923 | }else{ | |
| 3924 | window.appToast && window.appToast({ message: 'Nothing to Undo. History only stores 1 record.', type: 'warning' }); | |
| 3925 | } | |
| 3926 | } | |
| 3927 | this.undo_delete_check = false; | |
| 3928 | } | |
| 3929 | // Function used to create our custom context menu for copying, deleting, or adding comments. | |
| 3930 | // Not to be called from the template | |
| 3931 | right_click_row(e){ | |
| 3932 | // "this" refers to the element that the right click is bound to, NOT THE COMPONENT. | |
| 3933 | // "this_reference" DOES REFER TO THE COMPONENT; | |
| 3934 | // Prevent default behavior (in this case, intercepting the context menu on right click) | |
| 3935 | e.preventDefault(); | |
| 3936 | // Not always guaranteed to have right clicked the table cell element itself, because we allow | |
| 3937 | // html to come in the json/populate the cells. Loop until the table cell is retrieved. | |
| 3938 | let active_cell = e.target; | |
| 3939 | if(active_cell.nodeName != 'TD'){ | |
| 3940 | let cell_retrieved = false; | |
| 3941 | while(!cell_retrieved){ | |
| 3942 | active_cell = active_cell.parentElement; | |
| 3943 | if(active_cell.nodeName == 'TD'){ | |
| 3944 | cell_retrieved=true; | |
| 3945 | } | |
| 3946 | } | |
| 3947 | } | |
| 3948 | console.log(active_cell); | |
| 3949 | //Obtain grid ID from cell title. | |
| 3950 | let grid_id = active_cell.title; | |
| 3951 | //Obtain component reference from the gridReference map. | |
| 3952 | let this_reference = window.gridReference.get(grid_id); | |
| 3953 | this_reference.right_click_source_cell = active_cell; | |
| 3954 | //Obtain the header key-name from the cell, to retrieve the data from the JSON, not the DOM element. | |
| 3955 | let header_key = active_cell.headers; | |
| 3956 | //Obtain the ID from the row | |
| 3957 | let id = this.id; | |
| 3958 | let pagination_number = Number(this_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML); | |
| 3959 | let context_menu_container = document.createElement('div'); | |
| 3960 | // Populate the context menu with the default options of Delete Row, Undo Delete, and Copy Value | |
| 3961 | context_menu_container.innerHTML = `<a href='javascript:' onClick="this.parentElement.remove(); window.gridReference.get('${grid_id}').show_delete_confirmation('${id}', '${grid_id}');"><i class=''></i> Delete Row </a></hr>`; | |
| 3962 | context_menu_container.innerHTML += `<a href='javascript:' onClick="this.parentElement.remove(); window.gridReference.get('${grid_id}').undo_delete_row(${pagination_number});"><i class=''></i> Undo Delete</a></hr>`; | |
| 3963 | context_menu_container.innerHTML += `<a href='javascript:' onClick="window.gridReference.get('${grid_id}').copy_cell_value('${id}', '${header_key}'); this.parentElement.remove();"><i class=''></i> Copy Cell Value</a></hr>`; | |
| 3964 | context_menu_container.innerHTML += `<a href='javascript:' onClick="window.gridReference.get('${grid_id}').insert_row('${grid_id}', -1); this.parentElement.remove();"><i class=''></i> Insert Row Before</a></hr>`; | |
| 3965 | context_menu_container.innerHTML += `<a href='javascript:' onClick="window.gridReference.get('${grid_id}').insert_row('${grid_id}', 0); this.parentElement.remove();"><i class=''></i> Insert Row After</a></hr>`; | |
| 3966 | ||
| 3967 | //Section for inserting links into notes fields | |
| 3968 | let inner_field_element = active_cell.children[0]; | |
| 3969 | if(inner_field_element.contentEditable == 'plaintext-only'){ | |
| 3970 | context_menu_container.innerHTML += `<a href='javascript:' onClick="window.gridReference.get('${grid_id}').hyperlink_handler(event); this.parentElement.remove();"><i class=''></i> Add/Edit Hyperlink</a></hr>`; | |
| 3971 | context_menu_container.innerHTML += `<a href='javascript:' onClick="window.gridReference.get('${grid_id}').remove_anchor_tag(event); this.parentElement.remove();"><i class=''></i> Remove Hyperlink</a></hr>`; | |
| 3972 | } | |
| 3973 | ||
| 3974 | //This is how the injected context menu options are set. context_menu_injection is a the provided html string, or defaults to empty. | |
| 3975 | context_menu_container.innerHTML += this_reference.context_menu_injection; | |
| 3976 | ||
| 3977 | context_menu_container.classList.add('context-menu'); | |
| 3978 | let bounds = this_reference.getBoundingClientRect(); | |
| 3979 | context_menu_container.style = `position: absolute; left: ${e.clientX - 5 - bounds.left}px; top: ${e.clientY + 25 - bounds.top}px; z-index:10000`; | |
| 3980 | context_menu_container.onmouseleave = (e) => {e.target.remove();}; | |
| 3981 | this_reference.shadowRoot.prepend(context_menu_container); | |
| 3982 | // this_reference.addEventListener('mouseleave', this_reference.clear_listeners); | |
| 3983 | } | |
| 3984 | hyperlink_handler(e){ | |
| 3985 | ||
| 3986 | let grid_root = this.shadowRoot; | |
| 3987 | let grid_component = grid_root.host; | |
| 3988 | let allow_multi_links = this.multiple_links; | |
| 3989 | const selection = grid_root.getSelection(); | |
| 3990 | console.log(selection); | |
| 3991 | let selection_type = selection.type; | |
| 3992 | ||
| 3993 | let hyperlink_form = document.createElement('div'); | |
| 3994 | hyperlink_form.id = 'hyperlink-form'; | |
| 3995 | hyperlink_form.classList.add('hyperlink-form-popup'); | |
| 3996 | // Create the div to contain the title and input field | |
| 3997 | let display_text_container = document.createElement('div'); | |
| 3998 | display_text_container.classList.add('field-holder'); | |
| 3999 | let display_title_container = document.createElement('p'); | |
| 4000 | display_title_container.innerHTML = 'Display Text'; | |
| 4001 | let display_input_field = document.createElement('input'); | |
| 4002 | display_input_field.type="text"; | |
| 4003 | display_input_field.id = "display-field"; | |
| 4004 | //Building inner component | |
| 4005 | display_text_container.appendChild(display_title_container); | |
| 4006 | display_text_container.appendChild(display_input_field); | |
| 4007 | // Create the div to contain the title and link field | |
| 4008 | let link_field_container = document.createElement('div'); | |
| 4009 | link_field_container.classList.add('field-holder'); | |
| 4010 | let link_title_container = document.createElement('p'); | |
| 4011 | link_title_container.innerHTML = "Hyperlink"; | |
| 4012 | let link_input_field = document.createElement('input'); | |
| 4013 | link_input_field.type = "text"; | |
| 4014 | link_input_field.id = "link-field"; | |
| 4015 | //Building inner component | |
| 4016 | link_field_container.appendChild(link_title_container); | |
| 4017 | link_field_container.appendChild(link_input_field); | |
| 4018 | //Build the buttons to finish out the form | |
| 4019 | let add_hyperlink_button = document.createElement('button'); | |
| 4020 | let edit_hyperlink_button = document.createElement('button'); | |
| 4021 | let cancel_hyperlink_button = document.createElement('button'); | |
| 4022 | add_hyperlink_button.innerText = 'Add Hyperlink' | |
| 4023 | add_hyperlink_button.id = 'create-link-button'; | |
| 4024 | add_hyperlink_button.style.display = "none"; | |
| 4025 | edit_hyperlink_button.innerText = 'Update Hyperlink' | |
| 4026 | edit_hyperlink_button.id = 'edit-link-button'; | |
| 4027 | edit_hyperlink_button.style.display = "none"; | |
| 4028 | cancel_hyperlink_button.innerText = "Cancel"; | |
| 4029 | cancel_hyperlink_button.id = 'cancel-hyperlink-button'; | |
| 4030 | hyperlink_form.appendChild(display_text_container); | |
| 4031 | hyperlink_form.appendChild(link_field_container); | |
| 4032 | hyperlink_form.appendChild(add_hyperlink_button); | |
| 4033 | hyperlink_form.appendChild(edit_hyperlink_button); | |
| 4034 | hyperlink_form.appendChild(cancel_hyperlink_button); | |
| 4035 | ||
| 4036 | if(allow_multi_links){ | |
| 4037 | if(selection_type == 'Range'){ | |
| 4038 | let anchor_node = selection.anchorNode; | |
| 4039 | let anchor_tag = anchor_node.parentElement; | |
| 4040 | // Creating the form on the fly | |
| 4041 | //Create the container element for the form | |
| 4042 | add_hyperlink_button.onclick = this.create_anchor_tag; | |
| 4043 | edit_hyperlink_button.onclick = this.update_anchor_tag; | |
| 4044 | cancel_hyperlink_button.onclick = this.remove_anchor_form; | |
| 4045 | //Append everything into the form, hyperlink_form should now be complete and ready to display. | |
| 4046 | //Avoiding using innerHTML with a large html string to create this because we can lose listeners. | |
| 4047 | if(anchor_tag.nodeName == "A"){ | |
| 4048 | let display_text = selection.toString(); | |
| 4049 | display_input_field.value = display_text; | |
| 4050 | let display_link = anchor_tag.href; | |
| 4051 | link_input_field.value = display_link; | |
| 4052 | edit_hyperlink_button.style.display = 'flex'; | |
| 4053 | add_hyperlink_button.style.display = 'none'; | |
| 4054 | this.ANCHOR_NODE = anchor_node; | |
| 4055 | this.SELECTION_STRING = display_text; | |
| 4056 | ||
| 4057 | this.shadowRoot.appendChild(hyperlink_form); | |
| 4058 | }else{ | |
| 4059 | let anchor_offset = selection.anchorOffset; | |
| 4060 | let focus_offset = selection.focusOffset; | |
| 4061 | this.ANCHOR_NODE = anchor_node; | |
| 4062 | this.ANCHOR_OFFSET = anchor_offset; | |
| 4063 | this.FOCUS_OFFSET = focus_offset; | |
| 4064 | let display_text = selection.toString(); | |
| 4065 | this.SELECTION_STRING = display_text; | |
| 4066 | display_input_field.value = display_text; | |
| 4067 | edit_hyperlink_button.style.display = 'none'; | |
| 4068 | add_hyperlink_button.style.display = 'flex'; | |
| 4069 | ||
| 4070 | hyperlink_form.appendChild(display_text_container); | |
| 4071 | hyperlink_form.appendChild(link_field_container); | |
| 4072 | hyperlink_form.appendChild(add_hyperlink_button); | |
| 4073 | hyperlink_form.appendChild(edit_hyperlink_button); | |
| 4074 | hyperlink_form.appendChild(cancel_hyperlink_button); | |
| 4075 | this.shadowRoot.appendChild(hyperlink_form); | |
| 4076 | } | |
| 4077 | } | |
| 4078 | }else{ | |
| 4079 | ||
| 4080 | let cell_container = grid_component.right_click_source_cell; | |
| 4081 | let content_area = cell_container.querySelector('.contentarea'); | |
| 4082 | let content_anchor = content_area.querySelector('a'); | |
| 4083 | grid_component.CONTENT_NODE = content_area; | |
| 4084 | ||
| 4085 | if(content_anchor){ | |
| 4086 | // If single link and already has a link, we have to account for potential unlinked text | |
| 4087 | grid_component.ANCHOR_NODE = content_anchor.childNodes[0]; | |
| 4088 | let current_display = content_area.innerText; | |
| 4089 | let current_href = content_anchor.href; | |
| 4090 | display_input_field.value = current_display; | |
| 4091 | link_input_field.value = current_href; | |
| 4092 | edit_hyperlink_button.onclick = this.update_anchor_tag; | |
| 4093 | cancel_hyperlink_button.onclick = this.remove_anchor_form; | |
| 4094 | edit_hyperlink_button.style.display = 'flex'; | |
| 4095 | add_hyperlink_button.style.display = 'none'; | |
| 4096 | this.shadowRoot.appendChild(hyperlink_form); | |
| 4097 | }else{ | |
| 4098 | grid_component.ANCHOR_NODE = null; | |
| 4099 | display_text_container.style.display = 'none'; | |
| 4100 | let current_display = content_area.innerText; | |
| 4101 | let current_href = ''; | |
| 4102 | display_input_field.value = current_display; | |
| 4103 | link_input_field.value = current_href; | |
| 4104 | add_hyperlink_button.onclick = this.create_anchor_tag; | |
| 4105 | cancel_hyperlink_button.onclick = this.remove_anchor_form; | |
| 4106 | edit_hyperlink_button.style.display = 'none'; | |
| 4107 | add_hyperlink_button.style.display = 'flex'; | |
| 4108 | this.shadowRoot.appendChild(hyperlink_form); | |
| 4109 | } | |
| 4110 | } | |
| 4111 | } | |
| 4112 | create_anchor_tag(e){ | |
| 4113 | let grid_root = this.getRootNode(); | |
| 4114 | let grid_component = grid_root.host; | |
| 4115 | let allow_multi_links = this.multiple_links; | |
| 4116 | // text_node is either the selection text, or the entire div, depending on the multi_link flag | |
| 4117 | let text_node = grid_component.ANCHOR_NODE; | |
| 4118 | let content_node = grid_component.CONTENT_NODE; | |
| 4119 | let link_node = document.createElement("a"); | |
| 4120 | ||
| 4121 | let url_string = grid_root.getElementById('link-field').value; | |
| 4122 | let new_url; | |
| 4123 | // Assuming users will fail to always preppend https:// to their links, we will handle that for them | |
| 4124 | try{ | |
| 4125 | new_url = new URL(url_string); | |
| 4126 | }catch{ | |
| 4127 | console.log("Malformed URL, attempting to fix"); | |
| 4128 | new_url = new URL(`https://${url_string}`); | |
| 4129 | } | |
| 4130 | link_node.href = new_url; | |
| 4131 | link_node.title = `Ctrl-click to follow to ${new_url}`; | |
| 4132 | link_node.target = "_blank"; | |
| 4133 | link_node.contentEditable = "plaintext-only"; | |
| 4134 | let range = new Range(); | |
| 4135 | let form = grid_root.getElementById('hyperlink-form'); | |
| 4136 | let display_field = grid_root.getElementById('display-field'); | |
| 4137 | let link_field = grid_root.getElementById('link-field'); | |
| 4138 | if(allow_multi_links){ | |
| 4139 | let start_offset = grid_component.ANCHOR_OFFSET; | |
| 4140 | let end_offset = grid_component.FOCUS_OFFSET; | |
| 4141 | let selected_text = grid_component.SELECTION_STRING; | |
| 4142 | let last_char = selected_text.slice(-1); | |
| 4143 | ||
| 4144 | // Handling right to left, or left to right selections. | |
| 4145 | // Anchor Offset is where the selection started, focusOffset is where it ended, if selected right-to-left, focusOffset will be less than anchorOffset | |
| 4146 | if(start_offset < end_offset){ | |
| 4147 | if(last_char == " "){ | |
| 4148 | console.log("last char was a space"); | |
| 4149 | range.setStart(text_node, start_offset); | |
| 4150 | range.setEnd(text_node, end_offset - 1); | |
| 4151 | }else{ | |
| 4152 | range.setStart(text_node, start_offset); | |
| 4153 | range.setEnd(text_node, end_offset); | |
| 4154 | } | |
| 4155 | }else{ | |
| 4156 | if(last_char == " "){ | |
| 4157 | console.log("last char was a space"); | |
| 4158 | range.setStart(text_node, end_offset); | |
| 4159 | range.setEnd(text_node, start_offset); | |
| 4160 | }else{ | |
| 4161 | range.setStart(text_node, end_offset); | |
| 4162 | range.setEnd(text_node, start_offset - 1); | |
| 4163 | } | |
| 4164 | } | |
| 4165 | range.surroundContents(link_node); | |
| 4166 | display_field.value = ''; | |
| 4167 | link_field.value = ''; | |
| 4168 | form.remove(); | |
| 4169 | content_node.focus(); | |
| 4170 | content_node.blur(); | |
| 4171 | }else{ | |
| 4172 | // If the endNode is a Node of type Text, Comment, or CDataSection, then endOffset is the number of characters from the start of endNode. | |
| 4173 | // For other Node types (such as here which is a DIV), endOffset is the number of child nodes between the start of the endNode. | |
| 4174 | range.setStart(content_node, 0); | |
| 4175 | range.setEnd(content_node, 1); | |
| 4176 | range.surroundContents(link_node); | |
| 4177 | display_field.value = ''; | |
| 4178 | link_field.value = ''; | |
| 4179 | form.remove(); | |
| 4180 | // This forces a save, TO DO, potentially unwarranted side effects down the road. | |
| 4181 | // Text_node is the DIV here, because of only 1 potential link. it is content node above, because with multi links | |
| 4182 | // The text_node may be an actual text_node, it's parent an anchor tag, the ANCHOR's parent is the content node, | |
| 4183 | // here its text node -> content node | |
| 4184 | content_node.focus(); | |
| 4185 | content_node.blur(); | |
| 4186 | } | |
| 4187 | } | |
| 4188 | update_anchor_tag(e){ | |
| 4189 | let grid_root = this.getRootNode(); | |
| 4190 | let grid_component = grid_root.host; | |
| 4191 | let anchor_node = grid_component.ANCHOR_NODE; | |
| 4192 | let anchor_tag = anchor_node.parentElement; | |
| 4193 | let content_node = grid_component.CONTENT_NODE; | |
| 4194 | ||
| 4195 | let new_display_text = grid_root.getElementById('display-field').value; | |
| 4196 | let url_string = grid_root.getElementById('link-field').value; | |
| 4197 | let new_display_link; | |
| 4198 | // Assuming users will fail to always preppend https:// to their links, we will handle that for them | |
| 4199 | try{ | |
| 4200 | new_display_link = new URL(url_string); | |
| 4201 | }catch{ | |
| 4202 | console.log("Malformed URL, attempting to fix"); | |
| 4203 | new_display_link = new URL(`https://${url_string}`); | |
| 4204 | } | |
| 4205 | if(grid_component.multiple_links){ | |
| 4206 | anchor_tag.innerHTML = new_display_text; | |
| 4207 | anchor_tag.href = new_display_link; | |
| 4208 | let form = grid_root.getElementById('hyperlink-form'); | |
| 4209 | let display_field = grid_root.getElementById('display-field'); | |
| 4210 | let link_field = grid_root.getElementById('link-field'); | |
| 4211 | display_field.value = ''; | |
| 4212 | link_field.value = ''; | |
| 4213 | form.remove(); | |
| 4214 | content_node.focus(); | |
| 4215 | content_node.blur(); | |
| 4216 | }else{ | |
| 4217 | anchor_tag.innerHTML = new_display_text; | |
| 4218 | content_node.replaceChildren(anchor_tag); | |
| 4219 | let form = grid_root.getElementById('hyperlink-form'); | |
| 4220 | let display_field = grid_root.getElementById('display-field'); | |
| 4221 | let link_field = grid_root.getElementById('link-field'); | |
| 4222 | display_field.value = ''; | |
| 4223 | link_field.value = ''; | |
| 4224 | form.remove(); | |
| 4225 | content_node.focus(); | |
| 4226 | content_node.blur(); | |
| 4227 | } | |
| 4228 | } | |
| 4229 | remove_anchor_tag(e){ | |
| 4230 | let grid_component = this; | |
| 4231 | let grid_root = this.shadowRoot; | |
| 4232 | let allow_multi_links = this.multiple_links; | |
| 4233 | if(allow_multi_links){ | |
| 4234 | const selection = grid_root.getSelection(); | |
| 4235 | let selection_type = selection.type; | |
| 4236 | if(selection_type == 'Range'){ | |
| 4237 | let anchor_node = selection.anchorNode; | |
| 4238 | let parent_node = anchor_node.parentElement; | |
| 4239 | if(parent_node.nodeName == 'A'){ | |
| 4240 | const content_node = parent_node.parentElement; | |
| 4241 | console.log(content_node); | |
| 4242 | parent_node.replaceWith(selection.toString()); | |
| 4243 | console.log(content_node); | |
| 4244 | content_node.focus(); | |
| 4245 | content_node.blur(); | |
| 4246 | ||
| 4247 | } | |
| 4248 | ||
| 4249 | } | |
| 4250 | }else{ | |
| 4251 | let cell_container = grid_component.right_click_source_cell; | |
| 4252 | let content_node = grid_component.CONTENT_NODE; | |
| 4253 | if(!content_node){ | |
| 4254 | content_node = cell_container.querySelector('.contentarea'); | |
| 4255 | } | |
| 4256 | let display_text = content_node.innerText; | |
| 4257 | content_node.innerHTML = display_text; | |
| 4258 | content_node.focus(); | |
| 4259 | content_node.blur(); | |
| 4260 | } | |
| 4261 | } | |
| 4262 | anchor_click_handler(e){ | |
| 4263 | let target = e.target; | |
| 4264 | if(e.ctrlKey && target.nodeName == 'A'){ | |
| 4265 | let a_tag = target; | |
| 4266 | a_tag.contentEditable = false; | |
| 4267 | a_tag.click(); | |
| 4268 | a_tag.contentEditable = "plaintext-only"; | |
| 4269 | } else if (e.ctrlKey && target.nodeName == 'DIV'){ | |
| 4270 | let potential_anchor = target.querySelector("a"); | |
| 4271 | if(potential_anchor){ | |
| 4272 | ||
| 4273 | potential_anchor.contentEditable = false; | |
| 4274 | potential_anchor.click(); | |
| 4275 | potential_anchor.contentEditable = "plaintext-only"; | |
| 4276 | } | |
| 4277 | } | |
| 4278 | } | |
| 4279 | remove_anchor_form(e){ | |
| 4280 | let grid_root = e.target.getRootNode(); | |
| 4281 | let grid_component = grid_root.host; | |
| 4282 | grid_root.getElementById('hyperlink-form').remove(); | |
| 4283 | grid_component.ANCHOR_NODE = null; | |
| 4284 | grid_component.CONTENT_NODE = null; | |
| 4285 | grid_component.ANCHOR_OFFSET = null; | |
| 4286 | grid_component.FOCUS_OFFSET = null; | |
| 4287 | grid_component.SELECTION_STRING = null; | |
| 4288 | } | |
| 4289 | insert_row(grid_id, direction){ | |
| 4290 | ||
| 4291 | let this_reference = window.gridReference.get(grid_id); | |
| 4292 | let source_cell = this_reference.right_click_source_cell; | |
| 4293 | // Obtain the row_id from the comma seperated element ID; | |
| 4294 | // The direction determines whether we're inserting before or after. | |
| 4295 | let grid_row_id = Number(source_cell.getAttribute("id").split(",")[0]); | |
| 4296 | let database_row_id = Number(this_reference.GLOBAL_JSON_RESULT_FILTERED[grid_row_id]["row_number"]) + direction; | |
| 4297 | let insert_url = `${this_reference.insert_row_url}&row=${database_row_id}&iteration=${1}`; | |
| 4298 | console.log(`Grid Row: ${grid_row_id} Database Row: ${database_row_id}`); | |
| 4299 | console.log(insert_url); | |
| 4300 | document.body.style.cursor = "wait"; | |
| 4301 | this_reference.get_request(insert_url).then(result => { | |
| 4302 | this_reference.refresh_grid(); | |
| 4303 | }) | |
| 4304 | } | |
| 4305 | // This handles the excel keyboard commands, such as ctrl-c ctrl-v, tab, enter, and the arrow keys | |
| 4306 | // Not to be called from the template | |
| 4307 | excel_keydown_handler(e){ | |
| 4308 | // Get a reference to the grid object | |
| 4309 | let this_reference = window.gridReference.get(this.title); | |
| 4310 | // "this" will almost always refer to the <td> element, which we can get the positional ID from. | |
| 4311 | let current_position = this.id.split(','); | |
| 4312 | // ID's are oriented with row number, column number | |
| 4313 | let row_number = Number(current_position[0]); | |
| 4314 | let column_number = Number(current_position[1]); | |
| 4315 | let project_id = Number(current_position[2]); | |
| 4316 | let item_id = Number(current_position[3]); | |
| 4317 | let column_name = current_position[4]; | |
| 4318 | ||
| 4319 | // COLUMN_NAME_ORDER is created upon table creation, it lets us know the order of the columns based on the config | |
| 4320 | let column_name_position = this_reference.COLUMN_NAME_ORDER.indexOf(column_name); | |
| 4321 | let next_cell_id; | |
| 4322 | let next_cell; | |
| 4323 | // If they have a cell selected, CTRL-A will automatically focus and select the text in the input area. | |
| 4324 | if(e.shiftKey && !e.target.classList.contains("grid_table_data")){ | |
| 4325 | console.log("Shift-Enter"); | |
| 4326 | }else if(e.ctrlKey && e.code=="KeyA" && e.target.classList.contains("grid_table_data")){ | |
| 4327 | e.preventDefault(); | |
| 4328 | let input_element = e.target.querySelector("select,input,textarea"); | |
| 4329 | console.log("Ctrl-A"); | |
| 4330 | input_element.focus(); | |
| 4331 | console.log(input_element); | |
| 4332 | if(input_element.tagName == "INPUT" || input_element.tagName == "TEXTAREA"){ | |
| 4333 | console.log(input_element); | |
| 4334 | input_element.setSelectionRange(0, input_element.value.length); | |
| 4335 | } | |
| 4336 | // Up Arrow Navigation | |
| 4337 | }else if(e.code == "ArrowUp"){ | |
| 4338 | // Ignoring the shift key allows us to still use arrow keys to highlight within the cell input. | |
| 4339 | if(!e.shiftKey){ | |
| 4340 | e.preventDefault(); | |
| 4341 | row_number--; | |
| 4342 | if(row_number >= 0){ | |
| 4343 | // With the added insert/removal of rows, we can no longer guarantee item ID's follow eachother correctly. | |
| 4344 | let next_cell_primary_key = this_reference.GLOBAL_JSON_RESULT_FILTERED[row_number][this_reference.primary_key]; | |
| 4345 | next_cell_id = `${row_number},${column_number},${project_id},${next_cell_primary_key},${column_name}`; | |
| 4346 | next_cell = this_reference.shadowRoot.getElementById(next_cell_id); | |
| 4347 | next_cell.focus(); | |
| 4348 | next_cell.scrollIntoView({ | |
| 4349 | behavior: 'instant', | |
| 4350 | block: 'center', | |
| 4351 | inline: 'center' | |
| 4352 | }); | |
| 4353 | } | |
| 4354 | } | |
| 4355 | // Left Arrow Navigation | |
| 4356 | }else if(e.code == "ArrowLeft"){ | |
| 4357 | // Ignoring the shift key allows us to still use arrow keys to highlight within the cell input. | |
| 4358 | if(!e.shiftKey){ | |
| 4359 | e.preventDefault(); | |
| 4360 | column_number--; | |
| 4361 | ||
| 4362 | if(column_number >= 0){ | |
| 4363 | ||
| 4364 | next_cell_id = `${row_number},${column_number},${project_id},${item_id},${this_reference.COLUMN_NAME_ORDER[column_name_position-1]}`; | |
| 4365 | next_cell = this_reference.shadowRoot.getElementById(next_cell_id); | |
| 4366 | // table_container.scrollLeft -= next_cell.offsetWidth; | |
| 4367 | next_cell.scrollIntoView({ | |
| 4368 | behavior: 'instant', | |
| 4369 | block: 'center', | |
| 4370 | inline: 'center' | |
| 4371 | }); | |
| 4372 | next_cell.focus(); | |
| 4373 | ||
| 4374 | } | |
| 4375 | } | |
| 4376 | // Right Arrow and Tab Navigation | |
| 4377 | }else if(e.code == "ArrowRight" || (!e.target.classList.contains("grid_table_data") && e.code == "Tab")){ | |
| 4378 | // Ignoring the shift key allows us to still use arrow keys to highlight within the cell input. | |
| 4379 | if(!e.shiftKey){ | |
| 4380 | e.preventDefault(); | |
| 4381 | let max_columns = this_reference.COLUMN_NAME_ORDER.length; | |
| 4382 | column_number++; | |
| 4383 | ||
| 4384 | if(column_number < max_columns){ | |
| 4385 | next_cell_id = `${row_number},${column_number},${project_id},${item_id},${this_reference.COLUMN_NAME_ORDER[column_name_position+1]}`; | |
| 4386 | next_cell = this_reference.shadowRoot.getElementById(next_cell_id); | |
| 4387 | // table_container.scrollLeft += next_cell.offsetWidth | |
| 4388 | next_cell.scrollIntoView({ | |
| 4389 | behavior: 'instant', | |
| 4390 | block: 'center', | |
| 4391 | inline: 'center' | |
| 4392 | }); | |
| 4393 | next_cell.focus(); | |
| 4394 | } | |
| 4395 | } | |
| 4396 | // Down Arrow and Enter Navigation | |
| 4397 | }else if(e.code == "ArrowDown" || (!e.target.classList.contains("grid_table_data") && e.code == "Enter")){ | |
| 4398 | // Ignoring the shift key allows us to still use arrow keys to highlight within the cell input. | |
| 4399 | if(!e.shiftKey){ | |
| 4400 | e.preventDefault(); | |
| 4401 | let max_rows = this_reference.max_results; | |
| 4402 | row_number++; | |
| 4403 | try{ | |
| 4404 | if(row_number % max_rows != 0){ | |
| 4405 | let next_cell_primary_key = this_reference.GLOBAL_JSON_RESULT_FILTERED[row_number][this_reference.primary_key]; | |
| 4406 | next_cell_id = `${row_number},${column_number},${project_id},${next_cell_primary_key},${column_name}`; | |
| 4407 | next_cell = this_reference.shadowRoot.getElementById(next_cell_id); | |
| 4408 | next_cell.focus(); | |
| 4409 | next_cell.scrollIntoView({ | |
| 4410 | behavior: 'instant', | |
| 4411 | block: 'center', | |
| 4412 | inline: 'center' | |
| 4413 | }); | |
| 4414 | } | |
| 4415 | }catch{ | |
| 4416 | // We don't care if it errors. | |
| 4417 | } | |
| 4418 | } | |
| 4419 | }else if(e.target.classList.contains("grid_table_data") && (e.code !== "KeyV" && !e.ctrlKey)){ | |
| 4420 | // e.preventDefault(); | |
| 4421 | let input_element = e.target.querySelector("select,input,textarea"); | |
| 4422 | ||
| 4423 | input_element.focus(); | |
| 4424 | if(input_element.tagName == "INPUT" || input_element.tagName == "TEXTAREA"){ | |
| 4425 | input_element.setSelectionRange(input_element.value.length, input_element.value.length); | |
| 4426 | } | |
| 4427 | ||
| 4428 | // this.selectionStart = this.selectionEnd = this.value.length; | |
| 4429 | } | |
| 4430 | } | |
| 4431 | // Handler for pasting from excel. | |
| 4432 | // Not to be called from the template | |
| 4433 | copy_handler(e){ | |
| 4434 | let grid_reference = window.gridReference.get(e.currentTarget.title); | |
| 4435 | let active_element = grid_reference.shadowRoot.activeElement; | |
| 4436 | let row_change_array = []; | |
| 4437 | let undo_row_change_array = []; | |
| 4438 | // If the activeElement is the <td class="grid_table_data">, then we are currently clicked on the cell, not the inner form field. | |
| 4439 | // That is the paste target we want to handle, so prevent default. | |
| 4440 | if(active_element.classList.contains("grid_table_data")){ | |
| 4441 | e.preventDefault(); | |
| 4442 | ||
| 4443 | let position_array = active_element.id.split(','); | |
| 4444 | let row_position = Number(position_array[0]); | |
| 4445 | let column_position = Number(position_array[1]); | |
| 4446 | let project_id = Number(position_array[2]); | |
| 4447 | let project_item_id = Number(position_array[3]); | |
| 4448 | let used_columns = []; | |
| 4449 | //Convert the string back into a variable and dereference the data | |
| 4450 | Object.keys(grid_reference.configuration["column_information"]).forEach((key) => { | |
| 4451 | used_columns.push(key); | |
| 4452 | }); | |
| 4453 | let starting_column_key = used_columns[column_position]; | |
| 4454 | // Let the work begin here. | |
| 4455 | let clipboard_text = e.clipboardData.getData("text"); | |
| 4456 | // Replace last /r/n in the string (pasting from Excel on Mac does not include the trailing /r/n) | |
| 4457 | ||
| 4458 | clipboard_text = clipboard_text.replace(/[\r\n]+$/, ''); | |
| 4459 | ||
| 4460 | let clipboard_matrix = []; | |
| 4461 | //Excel separates its rows by /r/n, columns by tabs \t | |
| 4462 | let clipboard_rows = clipboard_text.split('\r\n'); | |
| 4463 | for(let i = 0; i < clipboard_rows.length; i++){ | |
| 4464 | let column_split = clipboard_rows[i].split("\t"); | |
| 4465 | let column_name_value = {}; | |
| 4466 | for(let j = 0; j < column_split.length; j++){ | |
| 4467 | if(used_columns[column_position+j]){ | |
| 4468 | column_name_value[used_columns[column_position+j]] = column_split[j]; | |
| 4469 | } | |
| 4470 | } | |
| 4471 | clipboard_matrix.push(column_name_value); | |
| 4472 | } | |
| 4473 | // let max_rows = Number(grid_reference.shadowRoot.getElementById(`${grid_reference.id}_max_results`).value); | |
| 4474 | // let max_rows = 1000; | |
| 4475 | let current_row_position = row_position; | |
| 4476 | let current_column_position = column_position; | |
| 4477 | // Empty the old data array, a new copy has occurred. | |
| 4478 | grid_reference.UNDO_COPY_JSON = []; | |
| 4479 | // Increase the count of how many times something has been copied (This is incase we want to expand upon it later) | |
| 4480 | grid_reference.COPY_COUNT++; | |
| 4481 | let over_copy_matrix = []; | |
| 4482 | let undo_over_copy_matrix = []; | |
| 4483 | let insert_copy_matrix = []; | |
| 4484 | let current_row_number = 0; | |
| 4485 | for(let i = 0; i < clipboard_matrix.length; i++){ | |
| 4486 | let copy_row = clipboard_matrix[i]; | |
| 4487 | // if((current_row_position+i) < max_rows){ | |
| 4488 | if((current_row_position+i) < grid_reference.GLOBAL_JSON_RESULT_FILTERED.length){ | |
| 4489 | let target_row = grid_reference.GLOBAL_JSON_RESULT_FILTERED[current_row_position+i]; | |
| 4490 | let column_names = Object.keys(copy_row); | |
| 4491 | let current_item_id = ''; | |
| 4492 | // HARDCODE: Believe this can be updated to be set with grid_reference.configuration['primary_key'] | |
| 4493 | if(target_row["az_bundle_id_assignments_id"]){ | |
| 4494 | current_item_id = target_row["az_bundle_id_assignments_id"]; | |
| 4495 | }else if(target_row["az_master_power_id"]){ | |
| 4496 | current_item_id = target_row["az_master_power_id"]; | |
| 4497 | }else{ | |
| 4498 | current_item_id = target_row["az_project_item_id"]; | |
| 4499 | } | |
| 4500 | copy_row["item_id"] = current_item_id; | |
| 4501 | copy_row["project_id"] = project_id; | |
| 4502 | ||
| 4503 | current_row_number = target_row["row_number"]; | |
| 4504 | copy_row["row_number"] = current_row_number; | |
| 4505 | ||
| 4506 | let undo_copy_row = Object.assign({}, copy_row); | |
| 4507 | over_copy_matrix.push(copy_row); | |
| 4508 | for(let j = 0; j < column_names.length; j++){ | |
| 4509 | if((current_column_position+j) < (used_columns.length)){ | |
| 4510 | ||
| 4511 | // TODO: Rework this section, shouldn't be duplicating code. Look at over_copy and the individual cells, we can marry them I think | |
| 4512 | let target_new_value = copy_row[column_names[j]]; | |
| 4513 | let target_original_value = target_row[used_columns[current_column_position+j]]; | |
| 4514 | let target_column_name = used_columns[current_column_position+j]; | |
| 4515 | undo_copy_row[column_names[j]] = target_original_value; | |
| 4516 | let copy_json = { | |
| 4517 | 'project_id': project_id, | |
| 4518 | 'project_item_id': current_item_id, | |
| 4519 | 'column_name': target_column_name, | |
| 4520 | 'original_value': target_original_value, | |
| 4521 | 'new_value': target_new_value, | |
| 4522 | } | |
| 4523 | let undo_json = { | |
| 4524 | 'project_id': project_id, | |
| 4525 | 'project_item_id': current_item_id, | |
| 4526 | 'column_name': target_column_name, | |
| 4527 | 'original_value': target_new_value, | |
| 4528 | 'new_value': target_original_value, | |
| 4529 | } | |
| 4530 | row_change_array.push(copy_json); | |
| 4531 | undo_row_change_array.push(undo_json); | |
| 4532 | }else{ | |
| 4533 | break; | |
| 4534 | } | |
| 4535 | } | |
| 4536 | undo_over_copy_matrix.push(undo_copy_row); | |
| 4537 | grid_reference.UNDO_OVER_COPY_JSON = Array.from(undo_over_copy_matrix); | |
| 4538 | grid_reference.UNDO_COPY_JSON = Array.from(undo_row_change_array); | |
| 4539 | }else{ | |
| 4540 | // Increment by 1 from the previous max | |
| 4541 | current_row_number++; | |
| 4542 | copy_row["item_id"] = ''; | |
| 4543 | copy_row["project_id"] = project_id; | |
| 4544 | copy_row["row_number"] = current_row_number; | |
| 4545 | insert_copy_matrix.push(copy_row); | |
| 4546 | ||
| 4547 | } | |
| 4548 | } | |
| 4549 | console.log(over_copy_matrix); | |
| 4550 | console.log(insert_copy_matrix); | |
| 4551 | grid_reference.modified_data["action"] = "excel_paste"; | |
| 4552 | grid_reference.modified_data["grid_name"] = grid_reference.getAttribute("name"); | |
| 4553 | grid_reference.modified_data["source_data"] = row_change_array; | |
| 4554 | grid_reference.modified_data["over_copy_data"] = over_copy_matrix; | |
| 4555 | grid_reference.modified_data["insert_copy_data"] = insert_copy_matrix; | |
| 4556 | grid_reference.save_data( | |
| 4557 | `grid-${grid_reference.grid_name}` | |
| 4558 | , Number(grid_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML) | |
| 4559 | ,'excel_paste' | |
| 4560 | ); | |
| 4561 | }else{ | |
| 4562 | console.log("Pasting inside the form field."); | |
| 4563 | } | |
| 4564 | } | |
| 4565 | // Undo most recent paste from Excel, HAS NOTHING TO DO WITH THE CONTEXT MENU UNDO | |
| 4566 | // How To Template Call: window.gridReference.get('grid-id-here').undo_copy(event); | |
| 4567 | undo_copy(e){ | |
| 4568 | let grid_reference = window.gridReference.get(e.target.getAttribute('grid-owner')); | |
| 4569 | if(grid_reference.COPY_COUNT > 0){ | |
| 4570 | grid_reference.modified_data = {}; | |
| 4571 | grid_reference.modified_data["action"] = "excel_paste"; | |
| 4572 | grid_reference.modified_data["grid_name"] = grid_reference.getAttribute("name"); | |
| 4573 | grid_reference.modified_data["source_data"] = grid_reference.UNDO_COPY_JSON; | |
| 4574 | grid_reference.modified_data["over_copy_data"] = grid_reference.UNDO_OVER_COPY_JSON; | |
| 4575 | if(grid_reference.UNDO_INSERT_STARTING_ID > 0){ | |
| 4576 | grid_reference.modified_data["undo_insert_id"] = grid_reference.UNDO_INSERT_STARTING_ID; | |
| 4577 | grid_reference.modified_data["undo_insert_amount"] = grid_reference.UNDO_INSERT_AMOUNT; | |
| 4578 | } | |
| 4579 | //TODO, update this to wipe out the inserted data. | |
| 4580 | grid_reference.modified_data["insert_copy_data"] = []; | |
| 4581 | grid_reference.save_data( | |
| 4582 | `grid-${grid_reference.grid_name}` | |
| 4583 | , Number(grid_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML) | |
| 4584 | ,'excel_paste' | |
| 4585 | ); | |
| 4586 | grid_reference.COPY_COUNT = 0; | |
| 4587 | grid_reference.UNDO_INSERT_STARTING_ID = -1; | |
| 4588 | grid_reference.UNDO_INSERT_AMOUNT = -1; | |
| 4589 | } | |
| 4590 | } | |
| 4591 | // The grid will enter excel mode, this is a boolean call that flips the grid between excel-mode and default-mode | |
| 4592 | // How To Template Call: window.gridReference.get('grid-id-here').excel_mode(event); | |
| 4593 | excel_mode(e){ | |
| 4594 | let grid_reference = window.gridReference.get(this.getAttribute("grid-owner")); | |
| 4595 | let grid_id = grid_reference.id; | |
| 4596 | if(grid_reference.excel_edit){ | |
| 4597 | grid_reference.excel_edit = false; | |
| 4598 | }else{ | |
| 4599 | grid_reference.excel_edit = true; | |
| 4600 | } | |
| 4601 | let current_page = Number(grid_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML); | |
| 4602 | grid_reference.create_table(grid_id, '', current_page); | |
| 4603 | } | |
| 4604 | // Function called by OUTSIDE helper functions, this simulates refreshing the page. | |
| 4605 | // How To Template Call: window.gridReference.get('grid-id-here').refresh_grid(); | |
| 4606 | refresh_grid(){ | |
| 4607 | let this_reference = this; | |
| 4608 | ||
| 4609 | let active_element = this.shadowRoot.activeElement; | |
| 4610 | console.log(this.shadowRoot); | |
| 4611 | console.log(active_element); | |
| 4612 | if(active_element){ | |
| 4613 | ||
| 4614 | if(active_element.tagName != 'SELECT' && active_element.tagName != "INPUT" && active_element.tagName != "TEXTAREA" && active_element.tagName != "TD"){ | |
| 4615 | console.log("Loading new table"); | |
| 4616 | document.body.style.cursor = "wait"; | |
| 4617 | this.shadowRoot.getElementById(`grid-${this.grid_name}_container`).classList.add('disabled-mouse'); | |
| 4618 | if(this.load_filters_url){ | |
| 4619 | this.load_global_json(this.load_filters_url).then(jsonData =>{ | |
| 4620 | // Escape the loaded json, making sure new line, carriage return, tab, etc are properly escaped in the json. | |
| 4621 | let filtered_filters = this.escape_json(jsonData); | |
| 4622 | this.GLOBAL_LOADED_FILTERS = filtered_filters; | |
| 4623 | ||
| 4624 | this.grid_filters = this.GLOBAL_LOADED_FILTERS; | |
| 4625 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 4626 | ||
| 4627 | let filtered_data = this.escape_json(jsonData); | |
| 4628 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 4629 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 4630 | this.create_table(`grid-${this.grid_name}`, '', Number(this.shadowRoot.querySelector('.pagination_selected_link').innerHTML)); | |
| 4631 | }); | |
| 4632 | }); | |
| 4633 | }else{ | |
| 4634 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 4635 | ||
| 4636 | let filtered_data = this.escape_json(jsonData); | |
| 4637 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 4638 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 4639 | this.create_table(`grid-${this.grid_name}`, '', Number(this.shadowRoot.querySelector('.pagination_selected_link').innerHTML)); | |
| 4640 | }); | |
| 4641 | } | |
| 4642 | }else{ | |
| 4643 | console.log("Not Loading New Table") | |
| 4644 | if(!active_element.onblur){ | |
| 4645 | // If trying to set directly to the timeout, it will not work,the timeout will go global, and it just fires recursively. | |
| 4646 | // Setting the timeout in an anonymous function allows the onBlur to be the function that sets the timeout, and removes itself. | |
| 4647 | active_element.addEventListener('blur', function(e){ | |
| 4648 | setTimeout(() => { | |
| 4649 | console.log('replacingstuff'); | |
| 4650 | if(!this_reference.excel_edit){ | |
| 4651 | this.contentEditable='false' | |
| 4652 | } | |
| 4653 | this_reference.refresh_grid(); | |
| 4654 | active_element.onblur = null | |
| 4655 | }, 1000); | |
| 4656 | }); | |
| 4657 | } | |
| 4658 | } | |
| 4659 | }else{ | |
| 4660 | console.log("Loading new table NO ACTIVE ELEMENT"); | |
| 4661 | this.shadowRoot.getElementById(`grid-${this.grid_name}_container`).classList.add('disabled-mouse'); | |
| 4662 | document.body.style.cursor = "wait"; | |
| 4663 | if(this.load_filters_url){ | |
| 4664 | this.load_global_json(this.load_filters_url).then(jsonData =>{ | |
| 4665 | // Escape the loaded json, making sure new line, carriage return, tab, etc are properly escaped in the json. | |
| 4666 | let filtered_filters = this.escape_json(jsonData); | |
| 4667 | this.GLOBAL_LOADED_FILTERS = filtered_filters; | |
| 4668 | ||
| 4669 | this.grid_filters = this.GLOBAL_LOADED_FILTERS; | |
| 4670 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 4671 | ||
| 4672 | let filtered_data = this.escape_json(jsonData); | |
| 4673 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 4674 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 4675 | this.create_table(`grid-${this.grid_name}`, '', Number(this.shadowRoot.querySelector('.pagination_selected_link').innerHTML)); | |
| 4676 | }); | |
| 4677 | }); | |
| 4678 | }else{ | |
| 4679 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 4680 | ||
| 4681 | let filtered_data = this.escape_json(jsonData); | |
| 4682 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 4683 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 4684 | this.create_table(`grid-${this.grid_name}`, '', Number(this.shadowRoot.querySelector('.pagination_selected_link').innerHTML)); | |
| 4685 | }); | |
| 4686 | } | |
| 4687 | } | |
| 4688 | ||
| 4689 | } | |
| 4690 | // Function called by OUTSIDE helper functions, this redraws the grid, without calling to the back for data. | |
| 4691 | // Used in conjunction with updating the JSON from outside the component. | |
| 4692 | // How To Template Call: window.gridReference.get('grid-id-here').redraw_grid(); | |
| 4693 | redraw_grid(){ | |
| 4694 | let this_reference = this; | |
| 4695 | let pagination_number = 1; | |
| 4696 | if(this_reference.shadowRoot.querySelector('.pagination_selected_link')){ | |
| 4697 | pagination_number = Number(this_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML); | |
| 4698 | } | |
| 4699 | this_reference.create_table(`grid-${this_reference.grid_name}`, '', pagination_number); | |
| 4700 | } | |
| 4701 | // Does nothing ignore this, its for dev work. | |
| 4702 | test_function(){ | |
| 4703 | console.log("This is an injected function interoperating within the webcomponent."); | |
| 4704 | } | |
| 4705 | // Prevent the user from accidently pressing delete. This highlights the clicked row and pops up a confirmation box. | |
| 4706 | // Not to be called from the template. | |
| 4707 | show_delete_confirmation(id, grid_id){ | |
| 4708 | let pending_row = this.shadowRoot.querySelector(`#${id}`); | |
| 4709 | pending_row.style.backgroundColor = '#ffaaaa'; | |
| 4710 | console.log(pending_row); | |
| 4711 | let confirmation_container = document.createElement('div'); | |
| 4712 | confirmation_container.innerHTML = ` | |
| 4713 | <div class="confirmation_container_display"> | |
| 4714 | <h2> Are you sure you want to delete this row? </h2> | |
| 4715 | <div class="confirmation_button_container"> | |
| 4716 | <button onclick="this.parentElement.parentElement.parentElement.remove(); window.gridReference.get('${grid_id}').shadowRoot.querySelector('#${id}').style.backgroundColor = '';"> No </button> | |
| 4717 | <button onclick="this.parentElement.parentElement.parentElement.remove(); window.gridReference.get('${grid_id}').delete_row('${id}');"> Yes </button> | |
| 4718 | </div> | |
| 4719 | </div> | |
| 4720 | ` | |
| 4721 | confirmation_container.id = 'confirmation-container'; | |
| 4722 | confirmation_container.classList.add('confirmation_container'); | |
| 4723 | window.gridReference.get(grid_id).shadowRoot.appendChild(confirmation_container); | |
| 4724 | // document.body.appendChild(confirmation_container); | |
| 4725 | // Prevent the user from being able to scroll away from the delete prompt. | |
| 4726 | confirmation_container.addEventListener('wheel', (e) =>{ | |
| 4727 | e.preventDefault(); | |
| 4728 | e.stopPropagation(); | |
| 4729 | ||
| 4730 | return false;}, | |
| 4731 | {passive: false} | |
| 4732 | ); | |
| 4733 | confirmation_container.children[0].style.top = window.scrollY + 'px'; | |
| 4734 | } | |
| 4735 | //using DEPRECATED functions, because we are serving over HTTP and not HTTPS. Navigator.clipboard.writeText only works in HTTPS contexts. | |
| 4736 | // Custom contextmenu to allow you to copy the value out of a field, useful for dropdowns and textareas. | |
| 4737 | // Not to be called from the template. | |
| 4738 | copy_cell_value(row_id, header_key){ | |
| 4739 | let row_json = this.GLOBAL_JSON_RESULT[Number(row_id.replace("row", ""))]; | |
| 4740 | ||
| 4741 | // This whole section is the deprecated functionality. | |
| 4742 | // ################################################################################# | |
| 4743 | let text_area_holder = document.createElement("textarea"); | |
| 4744 | this.shadowRoot.appendChild(text_area_holder); | |
| 4745 | text_area_holder.value = row_json[header_key]; | |
| 4746 | text_area_holder.select(); | |
| 4747 | document.execCommand('copy', false, row_json[header_key]); | |
| 4748 | text_area_holder.remove(); | |
| 4749 | // ################################################################################# | |
| 4750 | // END DEPRECATED FUNCTIONALITY | |
| 4751 | ||
| 4752 | // Modern usage, unservable over HTTP. | |
| 4753 | // navigator.clipboard.writeText(row_json[header_key][0]); | |
| 4754 | } | |
| 4755 | // Used in the copy drag functionality, to highlight the starting cell, and apply the correct listeners for dragging around. | |
| 4756 | // Not to be called from the template. | |
| 4757 | cell_down_handler(e){ | |
| 4758 | // Make sure right clicking does not trigger the copy-drag process | |
| 4759 | console.log("Down handler"); | |
| 4760 | if(e.which == 1){ | |
| 4761 | let this_reference = window.gridReference.get(e.target.parentElement.parentElement.title); | |
| 4762 | let table_container = this_reference.shadowRoot.querySelector("table"); | |
| 4763 | this_reference.starting_cell = e.target.parentElement.parentElement; | |
| 4764 | this_reference.starting_cell.classList.add('copied_over'); | |
| 4765 | this_reference.ending_cell = null; | |
| 4766 | this_reference.copy_lock = true; | |
| 4767 | table_container.classList.add("grid-dragging"); | |
| 4768 | this_reference.addEventListener('mousemove', this_reference.cell_copy_move_handler) | |
| 4769 | // this_reference.addEventListener('mouseleave', this_reference.clear_listeners); | |
| 4770 | } | |
| 4771 | ||
| 4772 | } | |
| 4773 | // Escape hatch for any hanging listeners, this is called when the mouse leaves the grid area, to prevent any hanging behavior. | |
| 4774 | // Not to be called from the template. | |
| 4775 | clear_listeners(e){ | |
| 4776 | console.log('CLEAR LISTENERS'); | |
| 4777 | let this_reference = e.target; | |
| 4778 | ||
| 4779 | for(let i = 0; i < this_reference.copy_element_array.length; i++){ | |
| 4780 | this_reference.copy_element_array[i].classList.remove("copied_over"); | |
| 4781 | this_reference.copy_element_array[i].classList.remove("copied_over_wrong"); | |
| 4782 | } | |
| 4783 | let dangling_menus = this_reference.shadowRoot.querySelectorAll(".context-menu"); | |
| 4784 | for(let menu of dangling_menus){ | |
| 4785 | menu.remove(); | |
| 4786 | } | |
| 4787 | //Reset all variables that held data from the cells. | |
| 4788 | this_reference.starting_cell = null; | |
| 4789 | this_reference.ending_cell = null; | |
| 4790 | this_reference.copy_lock = false; | |
| 4791 | this_reference.modified_data = {}; | |
| 4792 | this_reference.copy_element_array = []; | |
| 4793 | this_reference.removeEventListener('mousemove', this_reference.cell_copy_move_handler); | |
| 4794 | this_reference.removeEventListener('mouseleave', this_reference.clear_listeners); | |
| 4795 | this_reference.create_table(`grid-${this_reference.grid_name}`, '', Number(this_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML)) | |
| 4796 | } | |
| 4797 | //This is a nightmare and needs to be cleaned up. | |
| 4798 | // TODO: CLEAN! | |
| 4799 | // When the user has finished copy dragging, this gathers the cells between the start and finish line, regardless of which column | |
| 4800 | // the user ended in. | |
| 4801 | // Not to be called from the template. | |
| 4802 | cell_up_handler(e){ | |
| 4803 | let this_reference = window.gridReference.get(this.title); | |
| 4804 | let table_container = this_reference.shadowRoot.querySelector("table"); | |
| 4805 | if(table_container.classList.contains("grid-dragging")){ | |
| 4806 | table_container.classList.remove("grid-dragging"); | |
| 4807 | } | |
| 4808 | //Don't do the copy handling if right click was pressed. | |
| 4809 | this.style.cursor = "wait"; | |
| 4810 | if(e.which == 1){ | |
| 4811 | ||
| 4812 | //Handling single cell copy attempts | |
| 4813 | if(this_reference.starting_cell){ | |
| 4814 | this_reference.starting_cell.classList.remove('copied_over'); | |
| 4815 | } | |
| 4816 | //Clear copy element array, we're done copying. | |
| 4817 | for(let i = 0; i < this_reference.copy_element_array.length; i++){ | |
| 4818 | this_reference.copy_element_array[i].classList.remove("copied_over"); | |
| 4819 | } | |
| 4820 | //Remove the listener from the component, we don't need to process any more cells. | |
| 4821 | this_reference.removeEventListener('mousemove', this_reference.cell_copy_move_handler); | |
| 4822 | //If ending_cell is null and starting cell is not null, we know we're in the correct situation to copy. | |
| 4823 | if(this_reference.ending_cell == null && this_reference.starting_cell && this_reference.starting_cell != e.target){ | |
| 4824 | //Get the cell that was mouse-up'd over. | |
| 4825 | let active_cell = e.target; | |
| 4826 | //Occasionally they'll let up on the mouse while hovering over the embedded html, or the drag icon itself | |
| 4827 | //Loop through the parent elements until we have the table cell. | |
| 4828 | if(active_cell.nodeName != 'TD'){ | |
| 4829 | let cell_retrieved = false; | |
| 4830 | while(!cell_retrieved){ | |
| 4831 | active_cell = active_cell.parentElement; | |
| 4832 | if(active_cell.nodeName == 'TD'){ | |
| 4833 | cell_retrieved=true; | |
| 4834 | } | |
| 4835 | } | |
| 4836 | } | |
| 4837 | //If the active cell header is the same as the starting cell header, we're in the same column. | |
| 4838 | if(active_cell.headers != this_reference.starting_cell.headers){ | |
| 4839 | let parent_row = active_cell.parentElement; | |
| 4840 | let column_name = this_reference.starting_cell.headers; | |
| 4841 | let current_row_number = Number(parent_row.id.replace("row", '')); | |
| 4842 | active_cell = this_reference.shadowRoot.querySelector(`#row${current_row_number}`).querySelector(`td[headers='']`); | |
| 4843 | } | |
| 4844 | if(active_cell.headers == this_reference.starting_cell.headers){ | |
| 4845 | console.log("Same Row"); | |
| 4846 | //Set the ending cell | |
| 4847 | this_reference.ending_cell = active_cell; | |
| 4848 | //Get the indexes to pull the data out of the global json objects. | |
| 4849 | let starting_cell_index = Number(this_reference.starting_cell.parentElement.id.replace("row","")); | |
| 4850 | let ending_cell_index = Number(this_reference.ending_cell.parentElement.id.replace("row","")); | |
| 4851 | //Same as row deletion, if the 2 json objects are the same length, we're not filtering. | |
| 4852 | try{ | |
| 4853 | this_reference.modified_data["action"] = "copy"; | |
| 4854 | this_reference.modified_data["grid_name"] = this_reference.getAttribute("name"); | |
| 4855 | ||
| 4856 | let copy_target_array = []; | |
| 4857 | if(this_reference.GLOBAL_JSON_RESULT.length == this_reference.GLOBAL_JSON_RESULT_FILTERED.length){ | |
| 4858 | ||
| 4859 | console.log("Same Length"); | |
| 4860 | ||
| 4861 | this_reference.modified_data["source_column"] = active_cell.headers; | |
| 4862 | this_reference.modified_data["source_data"] = this_reference.GLOBAL_JSON_RESULT[starting_cell_index]; | |
| 4863 | console.log(this_reference.modified_data["source_data"]); | |
| 4864 | //Obtain the value from the JSON, and copy into the other indexes | |
| 4865 | let original_replacement_value = this_reference.GLOBAL_JSON_RESULT[starting_cell_index][active_cell.headers]; | |
| 4866 | if(original_replacement_value == null){ | |
| 4867 | original_replacement_value = ""; | |
| 4868 | } | |
| 4869 | let increment_regex = new RegExp('\\d*\\.?\\d*$'); | |
| 4870 | let leading_value = ''; | |
| 4871 | // exec returns an array of result, index, matching groups | |
| 4872 | let regex_array = increment_regex.exec(original_replacement_value); | |
| 4873 | console.log(regex_array); | |
| 4874 | let regex_result = regex_array[0]; | |
| 4875 | // The regex returned array is weird, the 0 index is the only accessible index by number. | |
| 4876 | // The rest of the entries are named, it's like a mix of an array and a object/dictionary/hashref | |
| 4877 | let trailing_number_start_index = regex_array["index"]; | |
| 4878 | let leading_string = original_replacement_value.slice(0, trailing_number_start_index); | |
| 4879 | let increment_start_value = Number(regex_result); | |
| 4880 | if(regex_result[0] == "0" && regex_result != "0"){ | |
| 4881 | leading_value = '0'; | |
| 4882 | } | |
| 4883 | let increment_amount = 1; | |
| 4884 | if(regex_array[0] != '' && this_reference.drag_increment_mode){ | |
| 4885 | for(let i = starting_cell_index+1; i <= ending_cell_index; i++){ | |
| 4886 | copy_target_array.push(this_reference.GLOBAL_JSON_RESULT[i]); | |
| 4887 | let final_incremented_value = `${leading_string}${leading_value}${increment_start_value + increment_amount}`; | |
| 4888 | increment_amount++; | |
| 4889 | this_reference.GLOBAL_JSON_RESULT[i][active_cell.headers] = final_incremented_value; | |
| 4890 | } | |
| 4891 | }else{ | |
| 4892 | for(let i = starting_cell_index+1; i <= ending_cell_index; i++){ | |
| 4893 | copy_target_array.push(this_reference.GLOBAL_JSON_RESULT[i]); | |
| 4894 | this_reference.GLOBAL_JSON_RESULT[i][active_cell.headers] = original_replacement_value; | |
| 4895 | } | |
| 4896 | } | |
| 4897 | //Handling filtered results. | |
| 4898 | }else{ | |
| 4899 | ||
| 4900 | console.log("Different Lengths"); | |
| 4901 | ||
| 4902 | this_reference.modified_data["source_column"] = active_cell.headers; | |
| 4903 | this_reference.modified_data["source_data"] = this_reference.GLOBAL_JSON_RESULT[starting_cell_index]; | |
| 4904 | ||
| 4905 | ||
| 4906 | let original_replacement_value = this_reference.GLOBAL_JSON_RESULT[starting_cell_index][active_cell.headers] | |
| 4907 | let increment_regex = new RegExp('\\d*\\.?\\d*$'); | |
| 4908 | let leading_value = ''; | |
| 4909 | // exec returns an array of result, index, matching groups | |
| 4910 | let regex_array = increment_regex.exec(original_replacement_value); | |
| 4911 | console.log(regex_array); | |
| 4912 | let regex_result = regex_array[0]; | |
| 4913 | // The regex returned array is weird, the 0 index is the only accessible index by number. | |
| 4914 | // The rest of the entries are named, it's like a mix of an array and a object/dictionary/hashref | |
| 4915 | let trailing_number_start_index = regex_array["index"]; | |
| 4916 | let leading_string = original_replacement_value.slice(0, trailing_number_start_index); | |
| 4917 | let increment_start_value = Number(regex_result); | |
| 4918 | if(regex_result[0] == "0" && regex_result != "0"){ | |
| 4919 | leading_value = '0'; | |
| 4920 | } | |
| 4921 | let increment_amount = 1; | |
| 4922 | if(regex_array[0] != '' && this_reference.drag_increment_mode){ | |
| 4923 | for(let i = starting_cell_index+1; i <= ending_cell_index; i++){ | |
| 4924 | copy_target_array.push(this_reference.GLOBAL_JSON_RESULT_FILTERED[i]); | |
| 4925 | let final_incremented_value = `${leading_string}${leading_value}${increment_start_value + increment_amount}`; | |
| 4926 | increment_amount++; | |
| 4927 | this_reference.GLOBAL_JSON_RESULT_FILTERED[i][active_cell.headers] = final_incremented_value; | |
| 4928 | } | |
| 4929 | }else{ | |
| 4930 | for(let i = starting_cell_index+1; i <= ending_cell_index; i++){ | |
| 4931 | copy_target_array.push(this_reference.GLOBAL_JSON_RESULT_FILTERED[i]); | |
| 4932 | this_reference.GLOBAL_JSON_RESULT_FILTERED[i][active_cell.headers] = original_replacement_value; | |
| 4933 | } | |
| 4934 | } | |
| 4935 | } | |
| 4936 | this_reference.modified_data["copy_targets"] = copy_target_array; | |
| 4937 | //REMOVE THIS | |
| 4938 | }catch(e){ | |
| 4939 | console.log(e); | |
| 4940 | window.appToast && window.appToast({ message: 'You set the primary key wrong.', type: 'error' }); | |
| 4941 | } | |
| 4942 | //Send the data to the server to save it | |
| 4943 | this_reference.save_data(`grid-${this_reference.grid_name}`, Number(this_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML), 'copy'); | |
| 4944 | //this_reference.create_table(`grid-${this_reference.grid_name}`, '', Number(this_reference.shadowRoot.querySelector('.pagination_selected_link').innerHTML)); | |
| 4945 | }else{ | |
| 4946 | window.appToast && window.appToast({ message: 'Cannot copy data into different columns.', type: 'error' }); | |
| 4947 | } | |
| 4948 | ||
| 4949 | ||
| 4950 | }else if(this_reference.copy_lock && this_reference.starting_cell == e.target){ | |
| 4951 | // Do nothing, can't copy into the same cell, that'd be silly. | |
| 4952 | }else if(this_reference.copy_lock){ | |
| 4953 | window.appToast && window.appToast({ message: 'Cannot copy data into different columns.', type: 'error' }); | |
| 4954 | } | |
| 4955 | //We're done copying, everything has finished, reset the cells. | |
| 4956 | this_reference.ending_cell = null; | |
| 4957 | this_reference.starting_cell = null; | |
| 4958 | this_reference.copy_lock = false; | |
| 4959 | } | |
| 4960 | this.style.cursor = "default"; | |
| 4961 | } | |
| 4962 | // Handler for moving while copy dragging, highlights passed over cells and adds them to a list to be processed by cell_up_handler | |
| 4963 | // Not to be called from the template. | |
| 4964 | cell_copy_move_handler(e){ | |
| 4965 | e.preventDefault(); | |
| 4966 | let this_reference = e.currentTarget; | |
| 4967 | const hovered_element = this_reference.shadowRoot.elementFromPoint(e.clientX, e.clientY); | |
| 4968 | ||
| 4969 | let column_name = this_reference.starting_cell.getAttribute("headers"); | |
| 4970 | let row_parent = hovered_element.closest("TR"); | |
| 4971 | let starting_row_number = Number(this_reference.starting_cell.parentElement.id.replace("row", '')); | |
| 4972 | let current_row_number = Number(row_parent.id.replace("row", '')); | |
| 4973 | let table_parent = row_parent.parentElement; | |
| 4974 | ||
| 4975 | let copy_array_index = this_reference.copy_element_array.indexOf(hovered_element); | |
| 4976 | let corrected_element; | |
| 4977 | if(this_reference.starting_cell.headers != hovered_element.headers){ | |
| 4978 | corrected_element = this_reference.shadowRoot.querySelector(`#row${current_row_number}`).querySelector(`td[headers='']`); | |
| 4979 | copy_array_index = this_reference.copy_element_array.indexOf(corrected_element); | |
| 4980 | } | |
| 4981 | ||
| 4982 | if(hovered_element.nodeName == "TD" && copy_array_index < 0){ | |
| 4983 | // If they're not hovering the correct column, we must grab and highlight the proper element. | |
| 4984 | if(this_reference.starting_cell.headers != hovered_element.headers){ | |
| 4985 | let corrected_element = this_reference.shadowRoot.querySelector(`#row${current_row_number}`).querySelector(`td[headers='']`); | |
| 4986 | let cell_targets = table_parent.querySelectorAll(`td[headers="${column_name}"]`); | |
| 4987 | for(let i = starting_row_number; i < current_row_number; i++){ | |
| 4988 | cell_targets[i].classList.add("copied_over"); | |
| 4989 | } | |
| 4990 | this_reference.copy_element_array.push(corrected_element); | |
| 4991 | }else{ | |
| 4992 | let cell_targets = table_parent.querySelectorAll(`td[headers="${column_name}"]`); | |
| 4993 | for(let i = starting_row_number; i < current_row_number; i++){ | |
| 4994 | cell_targets[i].classList.add("copied_over"); | |
| 4995 | } | |
| 4996 | this_reference.copy_element_array.push(hovered_element); | |
| 4997 | ||
| 4998 | } | |
| 4999 | }else if(hovered_element.nodeName == "TD" && (copy_array_index < this_reference.copy_element_array.length - 1)){ | |
| 5000 | const removed_element = this_reference.copy_element_array.pop(); | |
| 5001 | let cell_targets = table_parent.querySelectorAll(`td[headers="${column_name}"]`); | |
| 5002 | for(let i = cell_targets.length-1; i > current_row_number; i--){ | |
| 5003 | cell_targets[i].classList.remove("copied_over"); | |
| 5004 | } | |
| 5005 | } | |
| 5006 | } | |
| 5007 | // Used by the excel paste, copy drag, and delete actions, along with their respective undos. | |
| 5008 | // Uses the component variable modified data to keep track of what needs to be saved or not. | |
| 5009 | // Not to be called from the template. | |
| 5010 | save_data(grid_id, pagination_number, action){ | |
| 5011 | ||
| 5012 | if(action == "excel_paste" || action == "copy"){ | |
| 5013 | document.body.style.cursor = "wait"; | |
| 5014 | ||
| 5015 | } | |
| 5016 | let data_to_send = this.modified_data; | |
| 5017 | console.log(data_to_send); | |
| 5018 | let dummy_url = this.save_data_url; | |
| 5019 | ||
| 5020 | console.log(`Save Data Action to take: ${action}`); | |
| 5021 | let save_url_json_payload = `${dummy_url}${action}=1`; | |
| 5022 | this.send_json(save_url_json_payload, data_to_send).then(responseContent =>{ | |
| 5023 | if(responseContent.includes(',')){ | |
| 5024 | let split_response = responseContent.split(','); | |
| 5025 | let first_insert_id = split_response[0]; | |
| 5026 | let insert_amount = split_response[1]; | |
| 5027 | this.UNDO_INSERT_STARTING_ID = split_response[0]; | |
| 5028 | this.UNDO_INSERT_AMOUNT = split_response[1]; | |
| 5029 | } | |
| 5030 | this.load_global_json(this.data_url).then(jsonData =>{ | |
| 5031 | let filtered_data = this.escape_json(jsonData); | |
| 5032 | this.GLOBAL_JSON_RESULT = filtered_data; | |
| 5033 | this.GLOBAL_JSON_RESULT_FILTERED = Array.from(this.GLOBAL_JSON_RESULT); | |
| 5034 | document.body.style.cursor="default"; | |
| 5035 | ||
| 5036 | this.create_table(grid_id, '', pagination_number); | |
| 5037 | }); | |
| 5038 | }); | |
| 5039 | } | |
| 5040 | ||
| 5041 | } | |
| 5042 | // Helper function to be called by the template to generate this grid. Provided a json configuration file | |
| 5043 | // Refer to template for example | |
| 5044 | function generate_grid_config(config){ | |
| 5045 | let id = config["grid_id"]; | |
| 5046 | let load_data_url = config["load_url"]; | |
| 5047 | let save_data_url = config["save_url"]; | |
| 5048 | let load_filters_url = config["load_filters_url"]; | |
| 5049 | let save_filters_url = config["save_filters_url"]; | |
| 5050 | //! Micah edit | |
| 5051 | // Put new config items here | |
| 5052 | let check_highlight_row_url = config["check_highlight_row_url"]; | |
| 5053 | //! | |
| 5054 | let insert_row_url = config["insert_row_url"]; | |
| 5055 | // Apply the grid id to the load_filter url, as we need it upon grid load. | |
| 5056 | if(load_filters_url){ | |
| 5057 | load_filters_url += `&grid_id=grid-${id}`; | |
| 5058 | } | |
| 5059 | ||
| 5060 | let primary_key = config["primary_key"]; | |
| 5061 | let contextmenu = config["contextmenu"]; | |
| 5062 | let stylesheet_injection = config["stylesheet"]; | |
| 5063 | let toolbar_allowed = config["toolbar_allowed"]; | |
| 5064 | let excel_button = config["excel_button"]; | |
| 5065 | let increment_button = config["increment_button"]; | |
| 5066 | let drag_copy_features = config["drag_copy_features"]; | |
| 5067 | let filter_notification = config["filter_notification"]; | |
| 5068 | let zoom_button = config["zoom_button"]; | |
| 5069 | let new_grid = document.createElement("shawn-grid"); | |
| 5070 | let drag_increment_mode = config["drag_increment_mode"]; | |
| 5071 | let scroll_allowed = config["scroll_allowed"]; | |
| 5072 | let multiple_links = config["multiple_links"]; | |
| 5073 | let highlight_checkbox = config["highlight_checkbox"]; | |
| 5074 | let highlight_checked = config["highlight_checked"]; | |
| 5075 | let max_results = config["max_results"] || 50; | |
| 5076 | ||
| 5077 | ||
| 5078 | new_grid.setAttribute('name', id); | |
| 5079 | ||
| 5080 | ||
| 5081 | new_grid.configuration = config; | |
| 5082 | new_grid.contextmenu = contextmenu; | |
| 5083 | new_grid.data_url = load_data_url; | |
| 5084 | new_grid.save_data_url = save_data_url; | |
| 5085 | new_grid.insert_row_url = insert_row_url; | |
| 5086 | new_grid.save_filters_url = save_filters_url; | |
| 5087 | new_grid.load_filters_url = load_filters_url; | |
| 5088 | new_grid.check_highlight_row_url = check_highlight_row_url; | |
| 5089 | new_grid.primary_key = primary_key; | |
| 5090 | // This is a hardcoded value for the AZ_Project, still needs to be addressed | |
| 5091 | new_grid.url_identifier = 'project_id'; | |
| 5092 | new_grid.stylesheet_injection = stylesheet_injection; | |
| 5093 | new_grid.toolbar_allowed = toolbar_allowed; | |
| 5094 | new_grid.drag_increment_mode = drag_increment_mode; | |
| 5095 | new_grid.scroll_allowed = scroll_allowed; | |
| 5096 | new_grid.increment_button = increment_button; | |
| 5097 | new_grid.excel_button = excel_button; | |
| 5098 | new_grid.drag_copy_features = drag_copy_features; | |
| 5099 | new_grid.filter_notification = filter_notification; | |
| 5100 | new_grid.zoom_button = zoom_button; | |
| 5101 | new_grid.multiple_links = multiple_links; | |
| 5102 | new_grid.highlight_checkbox = highlight_checkbox; | |
| 5103 | new_grid.highlight_checked = highlight_checked; | |
| 5104 | new_grid.max_results = max_results; | |
| 5105 | ||
| 5106 | window.gridReference.set(`grid-${id}`, new_grid); | |
| 5107 | return new_grid; | |
| 5108 | } | |
| 5109 | window.gridReference = new Map(); | |
| 5110 | //Automatically define the grid when imported. | |
| 5111 | customElements.define('shawn-grid', ShawnGrid); |