1# stapi/display/display.tcl -- derived from diodisplay.tcl 2 3# Copyright 2006 Superconnect 4 5# diodisplay.tcl -- 6 7# Copyright 2002-2004 The Apache Software Foundation 8 9# Licensed under the Apache License, Version 2.0 (the "License"); 10# you may not use this file except in compliance with the License. 11# You may obtain a copy of the License at 12 13# http://www.apache.org/licenses/LICENSE-2.0 14 15# Unless required by applicable law or agreed to in writing, software 16# distributed under the License is distributed on an "AS IS" BASIS, 17# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18# See the License for the specific language governing permissions and 19# limitations under the License. 20# 21# $Id$ 22# 23 24package require Itcl 25package require form 26package require st_client 27package require stapi_extend 28 29# 30# Only load ::csv:: if it's actually wanted. 31# 32namespace eval ::stapi::display { 33 variable csv_loaded 0 34 proc load_csv {} { 35 variable csv_loaded 36 if $csv_loaded { 37 return 38 } 39 uplevel #0 package require csv 40 } 41} 42 43catch { ::itcl::delete class STDisplay } 44 45::itcl::class ::STDisplay { 46 constructor {args} { 47 eval configure $args 48 load_response 49 50 # allow 'ctable' instead of 'table' as a historical alias (interim) 51 if {![info exists table] && [info exists ctable]} { 52 set table $ctable 53 unset ctable 54 } 55 56 # If it's not already an extended table, treat it like a URI 57 if {[info exists table] && ![::stapi::extend::extended $table]} { 58 set uri $table 59 unset table 60 } 61 62 if {![info exists table]} { 63 if ![info exists uri] { 64 return -code error "No table or uri" 65 } 66 67 if ![info exists keyfields] { 68 if [info exists key] { 69 set keyfields [list $key] 70 } 71 } 72 73 if [info exists keyfields] { 74 set table [::stapi::connect $uri -keys $keyfields] 75 } else { 76 set table [::stapi::connect $uri] 77 } 78 } 79 80 if ![info exists keyfields] { 81 if [info exists key] { 82 set keyfields [list $key] 83 } else { 84 set mlist [$table methods] 85 86 if {[lsearch $mlist "key"] >= 0} { 87 set keyfields [list [$table key]] 88 } else { 89 set keyfields [$table keys] 90 } 91 } 92 } 93 94 if {![info exists key]} { 95 if {[llength $keyfields] == 1} { 96 set key [lindex $keyfields 0] 97 } 98 } 99 100 if {![info exists key]} { 101 set cause $table 102 if {[info exists uri]} { set cause $uri } 103 return -code error "No key or keyfields, and $cause doesn't know how to tell me" 104 } 105 106 if {[lempty $form]} { 107 set form [namespace which [::form #auto -defaults response]] 108 } 109 110 set document [env DOCUMENT_NAME] 111 112 if {[info exists response(num)] \ 113 && ![lempty $response(num)]} { 114 set pagesize $response(num) 115 } 116 117 read_css_file 118 } 119 120 destructor { 121 if {$cleanup} { do_cleanup } 122 } 123 124 method destroy {} { 125 ::itcl::delete object $this 126 } 127 128 method debug {args} { 129 set show $debug 130 131 if {"[lindex $args 0]" == "-force"} { 132 set show 1 133 set args [lrange $args 1 end] 134 } 135 if {$show} { 136 eval ::stapi::debug $args 137 } 138 } 139 140 ## Glue routines for the mismatch between DIO and remote ctables. 141 ## The way DIO builds SQL that can be exposed outside DIO in assembling 142 ## a request is used by DIODisplay. We have to make that more abstract 143 144 ## New exposed configvars for STDisplay 145 public variable table 146 public variable ctable ;# Alias 147 public variable uri 148 public variable keyfields 149 public variable key 150 public variable debug 0 151 152 ## Background configvars 153 private variable ct_selection 154 155 private variable case 156 157 # 158 # configvar - a convenient helper for creating methods that can 159 # set and fetch one of the object's variables 160 # 161 method configvar {varName string {defval ""}} { 162 if {"$string" == "$defval"} { return [set $varName] } 163 configure -$varName $string 164 } 165 166 # 167 # is_function - return true if name is known to be a function 168 # such as Search List Add Edit Delete Details Main Save DoDelete Cancel 169 # etc. 170 # 171 method is_function {name} { 172 if {[lsearch $functions $name] >= 0} { return 1 } 173 if {[lsearch $allfunctions $name] >= 0} { return 1 } 174 return 0 175 } 176 177 # 178 # do_cleanup - clean up our field subobjects, DIO objects, forms, and the 179 # like. 180 # 181 method do_cleanup {} { 182 ## Destroy all the fields created. 183 foreach field $allfields { catch { $field destroy } } 184 185 ## Destroy the form object. 186 catch { $form destroy } 187 } 188 189 # 190 # handle_error - emit an error message 191 # 192 method handle_error {error args} { 193 puts "<B>An error has occurred processing your request</B>" 194 puts "<PRE>" 195 if {$debug > 1} { 196 puts "" 197 if [llength $args] { 198 puts [join $args "\n\n"] 199 } else { 200 puts "$::errorInfo" 201 } 202 } 203 puts "$error" 204 puts "</PRE>" 205 } 206 207 # escape string for display within HTML 208 protected method escape_cgi {str} { 209 if {[catch {escape_sgml_chars $str} result] == 0} { 210 # we were running under Apache Rivet and could use its existing command. 211 return $result 212 } else { 213 # This duplicates the Rivet escape_sgml_chars command: 214 return [string map { & & < < > > " " ' ' } $str] 215 } 216 } 217 218 # escape string for creation of a URL 219 protected method escape_url {str} { 220 if {[catch {escape_string $str} result] == 0} { 221 # we were running under Apache Rivet and could use its existing command. 222 return $result 223 } else { 224 # TODO: this is not very good; should also hex-encode many other things. 225 foreach \ 226 src " & {\"} < > " \ 227 dst { {\&} {\"} {\<} {\>} } { 228 regsub -all $src $str $dst str 229 } 230 return $str 231 } 232 } 233 234 # minimal escape string for protecting HTML only 235 # Avoids changing more than necessary to avoid stepping on legacy filters 236 protected method escape_html {str} { 237 return [string map { & & < < > > } $str] 238 } 239 240 # 241 # read_css_file - parse and read in a CSS file so we can 242 # recognize CSS info and emit it in appropriate places 243 # 244 method read_css_file {} { 245 if {"$css_file" != ""} { 246 if {![catch {open [virtual_filename $css_file]} fp]} { 247 set contents [read $fp] 248 close $fp 249 } 250 } else { 251 foreach file $css_files { 252 if {![catch {open [virtual_filename $file]} fp]} { 253 set css_file $file 254 set contents [read $fp] 255 close $fp 256 } 257 } 258 } 259 if ![info exists contents] { 260 return 261 } 262 if {[catch {array set tmpArray $contents}]} { return } 263 foreach class [array names tmpArray] { 264 set cssArray([string toupper $class]) $tmpArray($class) 265 } 266 } 267 268 # 269 # get_css_class - figure out which CSS class we want to use. 270 # If class exists, we use that. If not, we use default. 271 # 272 method get_css_class {tag default class} { 273 274 # if tag.class exists, use that 275 if {[info exists cssArray([string toupper $tag.$class])]} { 276 return $class 277 } 278 279 # if .class exists, use that 280 if {[info exists cssArray([string toupper .$class])]} { 281 return $class 282 } 283 284 # use the default 285 return $default 286 } 287 288 # 289 # parse_css_class - given a class and the name of an array, parse 290 # the named CSS class (read from the style sheet) and return it as 291 # key-value pairs in the named array. 292 # 293 method parse_css_class {class arrayName} { 294 295 # if we don't have an entry for the specified class, give up 296 if {![info exists cssArray($class)]} { 297 return 298 } 299 300 # split CSS entry on semicolons, for each one... 301 upvar 1 $arrayName array 302 foreach line [split $cssArray($class) \;] { 303 304 # trim leading and trailing spaces 305 set line [string trim $line] 306 307 # split the line on a colon into property and value 308 lassign [split $line :] property value 309 310 # map the property to space-trimmed lowercase and 311 # space-trim the value, then store in the passed array 312 set property [string trim [string tolower $property]] 313 set value [string trim $value] 314 set array($property) $value 315 } 316 } 317 318 # 319 # button_image_src - return the value of the image-src element in 320 # the specified class (from the CSS style sheet), or an empty 321 # string if there isn't one. 322 # 323 method button_image_src {class} { 324 set class [string toupper input.$class] 325 parse_css_class $class array 326 if {![info exists array(image-src)]} { 327 return 328 } 329 return $array(image-src) 330 } 331 332 # state - return a list of name-value pairs that represents the current 333 # state of the query, which can be used to properly populate links 334 # outside STDisplay. 335 method state {} { 336 set state {} 337 foreach var {mode query by how sort rev num page} { 338 if [info exists response($var)] { 339 lappend state $var $response($var) 340 } 341 } 342 return $state 343 } 344 345 # DName - convert a field name to a display name 346 protected method DName {fld} { 347 if {[info exists NameTextMap($fld)]} { 348 return $fld 349 } 350 if {[info exists FieldNameMap($fld)]} { 351 return [$FieldNameMap($fld) text] 352 } 353 if {"$fld" == "_key"} { 354 return "-key-" 355 } 356 return $fld 357 } 358 359 # FName - convert a display or column to a field name 360 protected method FName {fld {complain 1}} { 361 if {[info exists NameTextMap($fld)]} { 362 set fld $NameTextMap($fld) 363 } 364 365 if {[info exists FieldNameMap($fld)]} { 366 set fld $FieldNameMap($fld) 367 } 368 369 if {[lsearch $fields $fld] < 0} { 370 if {$complain} { 371 return -code error "No field name for $fld" 372 } else { 373 return "" 374 } 375 } 376 return $fld 377 } 378 379 # CName - convert a field or column to a canonical name 380 protected method CName {fld {complain 1}} { 381 if {[info exists NameTextMap($fld)]} { 382 return $NameTextMap($fld) 383 } 384 385 if {[info exists FieldNameMap($fld)]} { 386 return $fld 387 } 388 389 if {[lsearch $fields $fld] < 0} { 390 if {"$fld" == "-key-"} { 391 if {[llength $keyfields] == 1} { 392 return [lindex $keyfields 0] 393 } else { 394 return _key 395 } 396 } 397 398 if {$complain} { 399 return -code error "No field name for $fld" 400 } 401 return "" 402 } 403 return [$fld name] 404 } 405 406 method show {} { 407 if {[llength $fields] <= 0} { 408 foreach key $keyfields { 409 if {"$key" == "_key"} { 410 field $key -text "Key" 411 } else { 412 set text $key 413 regsub -all {_} $text { } text 414 field $key -text [string totitle $text] 415 } 416 } 417 418 foreach fld [$table fields] { 419 if {[lsearch $keyfields $fld] < 0} { 420 set text $fld 421 regsub -all {_} $text { } text 422 field $fld -text [string totitle $text] 423 } 424 } 425 } 426 427 if {[llength $fields] <= 0} { 428 return -code error "No fields defined for display." 429 } 430 431 # If readonly get rid of write functions, sanitize mode 432 if {$readonly} { 433 set skipfunctions $writefunctions 434 if [info exists response(mode)] { 435 if {[lsearch $writefunctions $response(mode)] >= 0} { 436 set response(mode) List 437 } 438 } 439 } else { 440 set skipfunctions {} 441 } 442 443 # If no details, get rid of Details 444 if {!$details} { 445 lappend skipfunctions Details 446 } 447 448 if {[llength $skipfunctions]} { 449 foreach list {functions rowfunctions} { 450 set new {} 451 foreach fun [set $list] { 452 if {[lsearch $skipfunctions $fun] < 0} { 453 lappend new $fun 454 } 455 } 456 set $list $new 457 } 458 } 459 460 # if there's a mode in the response array, use that, else leave mode 461 # as the default (List unless caller specified otherwise) 462 if {[info exists response(mode)]} { 463 set mode $response(mode) 464 if {[string match "*-*" $mode]} { 465 set mode List 466 } elseif {[string match {*[+ ]*} $mode]} { 467 set mode List 468 add_search_to_selection 469 } 470 set response(mode) $mode 471 } 472 puts "<!--Generated by $this show, mode=$mode-->" 473 474 # sanitize "by": 475 # If it's empty, remove it. 476 # If it's a label, change it to a field 477 if [info exists response(by)] { 478 if {"$response(by)" == ""} { 479 unset response(by) 480 } else { 481 set response(by) [DName $response(by)] 482 } 483 } 484 485 # if there was a request to generate a CSV file, generate it 486 if {[info exists response(ct_csv)]} { 487 gencsvfile $response(ct_csv) 488 if $csvredirect { 489 headers redirect $csvurl 490 destroy 491 return 492 } 493 } 494 495 # if there is a style sheet defined, emit HTML to reference it 496 if {![lempty $css_file]} { 497 puts "<LINK REL=\"stylesheet\" TYPE=\"text/css\" HREF=\"$css_file\">" 498 } 499 500 # put out the table header 501 puts {<TABLE WIDTH="100%" CLASS="DIO">} 502 puts {<TR CLASS="DIO">} 503 puts {<TD VALIGN="center" CLASS="DIO">} 504 505 # if mode isn't Main and persistentmain is set (the default), 506 # use Main 507 if {$mode != "Main" && $persistentmain} { 508 Main 509 } 510 511 if {![is_function $mode]} { 512 puts "<H2>Invalid function '$mode'</H2>" 513 puts " 514 <P>This may be due to an error in an external link 515 or in this web page. You may be able to use the back 516 button in your browser to return to the previous page 517 and try this query again. If you continue to get this 518 error, please contact the operator of this system.</P>" 519 puts "</TD>" 520 puts "</TR>" 521 puts "</TABLE>" 522 return 523 } 524 525 if {[catch [list $this $mode] error]} { 526 puts "</TD>" 527 puts "</TR>" 528 puts "</TABLE>" 529 if !$trap_errors { 530 if {$cleanup} { destroy } 531 error $error $::errorInfo 532 } 533 puts "<H2>Internal Error</H2>" 534 handle_error "$this $mode => $error" 535 } 536 537 puts "</TD>" 538 puts "</TR>" 539 puts "</TABLE>" 540 541 if {$cleanup} { destroy } 542 } 543 544 method showview {} { 545 puts {<TABLE CLASS="DIOView">} 546 set row 0 547 foreach field $fields { 548 $field showview [lindex {"" "Alt"} $row] 549 set row [expr {1 - $row}] 550 } 551 puts "</TABLE>" 552 } 553 554 protected method hide_hidden_vars {f} { 555 foreach var [array names hidden] { 556 $f hidden $var -value [escape_cgi $hidden($var)] 557 } 558 } 559 560 protected method hide_selection {f {op ""} {val ""}} { 561 if {"$op" == "+"} { 562 set all 1 563 } else { 564 set all 0 565 } 566 set selection [get_selection $all] 567 568 if {"$op" == "-"} { 569 set first [lsearch $selection $val] 570 if {$first >= 0} { 571 set selection [lreplace $selection $first $first] 572 } 573 } 574 575 $f hidden ct_sel -value [escape_cgi $selection] 576 } 577 578 protected method hide_cgi_vars {f args} { 579 # Special cases first 580 if [info exists response(mode)] { 581 set val $response(mode) 582 583 if [string match {*[+ -]*} $val] { 584 set val [lindex {List Search} [info exists response(query)]] 585 } 586 $f hidden mode -value [escape_cgi $val] 587 } 588 589 # Just copy the rest 590 foreach cgi_var {query by how sort rev num} { 591 if {[lsearch $args $cgi_var] < 0} { 592 if [info exists response($cgi_var)] { 593 $f hidden $cgi_var -value [escape_cgi $response($cgi_var)] 594 } 595 } 596 } 597 } 598 599 # 600 # showform - emit a form for inserting a new record 601 # 602 # response(by) will contain whatever was in the "where" field 603 # response(query) will contain whatever was in the "is" field 604 # 605 method showform {} { 606 get_field_values array 607 608 set save [button_image_src DIOFormSaveButton] 609 set cancel [button_image_src DIOFormCancelButton] 610 611 $form start -method post -name save_form 612 #$form hidden pizza -value pepperoni 613 hide_hidden_vars $form 614 hide_selection $form 615 $form hidden mode -value Save 616 617 if [info exists response(mode)] { 618 $form hidden DIODfromMode -value [escape_cgi $response(mode)] 619 } 620 621 $form hidden DIODkey -value [escape_cgi [makekey array]] 622 puts {<TABLE CLASS="DIOForm">} 623 624 # emit the fields for each field using the showform method 625 # of the field. if they've typed something into the 626 # search field and it matches one of the fields in the 627 # record (and it should), put that in as the default 628 foreach field $fields { 629 set name [$field name] 630 631 if [info exists alias($name)] { 632 continue 633 } 634 635 if {[info exists response(by)] && $response(by) == $name} { 636 if {![$field readonly] && $response(query) != ""} { 637 $field value $response(query) 638 } 639 } 640 $field showform 641 } 642 puts "</TABLE>" 643 644 puts {<TABLE CLASS="DIOFormSaveButton">} 645 puts {<TR CLASS="DIOFormSaveButton">} 646 puts {<TD CLASS="DIOFormSaveButton">} 647 648 if {![lempty $save]} { 649 $form image save -src $save -class DIOFormSaveButton 650 } else { 651 $form submit save.x -value "Save" -class DIOFormSaveButton 652 } 653 puts "</TD>" 654 puts {<TD CLASS="DIOFormSaveButton">} 655 656 if {![lempty $cancel]} { 657 $form image cancel -src $cancel -class DIOFormSaveButton 658 } else { 659 $form submit cancel.x -value "Cancel" -class DIOFormCancelButton 660 } 661 puts "</TD>" 662 puts "</TR>" 663 puts "</TABLE>" 664 665 $form end 666 } 667 668 method page_buttons {end {count 0}} { 669 if {$pagesize <= 0} { return } 670 671 if {![info exists response(page)]} { set response(page) 1 } 672 673 set pref DIO$end 674 if {!$count} { 675 set count [perform request] 676 } 677 678 set pages [expr {($count + $pagesize - 1) / $pagesize}] 679 680 if {$pages <= 1} { 681 return 682 } 683 684 set first [expr {$response(page) - 3}] 685 if {$first > $pages - 5} { 686 set first [expr {$pages - 5}] 687 } 688 689 if {$first > 1} { 690 lappend pagelist 1 1 691 692 if {$first > 10} { 693 lappend pagelist ".." 0 694 set mid [expr {$first / 2}] 695 if {$mid > 20 && $response(page) > $pages - 20} { 696 set quarter [expr {$mid / 2}] 697 lappend pagelist $quarter $quarter 698 lappend pagelist ".." 0 699 } 700 701 if {$first < $pages - 4} { 702 set first [expr {$response(page) - 1}] 703 } 704 705 lappend pagelist $mid $mid 706 if {$first - $mid > 10 && $response(page) > $pages - 20} { 707 lappend pagelist ".." 0 708 set quarter [expr {( $first + $mid ) / 2}] 709 lappend pagelist $quarter $quarter 710 } 711 } 712 713 if {$first > 3} { 714 lappend pagelist ".." 0 715 } elseif {$first > 2} { 716 lappend pagelist 2 2 717 } 718 } else { 719 set first 1 720 } 721 722 set last [expr {$response(page) + 3}] 723 if {$last < $pages - 10 && $last > 3} { 724 set last [expr {$response(page) + 1}] 725 } 726 727 if {$last < 5} { 728 set last 5 729 } 730 731 if {$last > $pages} { 732 set last $pages 733 } 734 735 for {set i $first} {$i <= $last} {incr i} { 736 lappend pagelist $i $i 737 } 738 739 if {$last < $pages} { 740 if {$last < $pages - 2} { 741 lappend pagelist ".." 0 742 } elseif {$last < $pages - 1} { 743 incr last 744 lappend pagelist $last $last 745 } 746 747 if {$last < $pages - 10} { 748 set mid [expr {( $pages + $last ) / 2}] 749 if {$last < $mid - 10 && $response(page) < 20} { 750 set quarter [expr {( $mid + $last ) / 2}] 751 lappend pagelist $quarter $quarter 752 lappend pagelist ".." 0 753 } 754 755 lappend pagelist $mid $mid 756 lappend pagelist ".." 0 757 if {$mid < $pages - 20 && $response(page) < 20} { 758 set quarter [expr {( $mid + $pages ) / 2}] 759 lappend pagelist $quarter $quarter 760 lappend pagelist ".." 0 761 } 762 } 763 lappend pagelist $pages $pages 764 } 765 766 foreach {n p} $pagelist { 767 if {$p == 0 || $p == $response(page)} { 768 lappend navbar $n 769 } else { 770 set html "<A HREF=\"" 771 set list {} 772 773 foreach var {mode query by how sort rev num} { 774 if {[info exists response($var)]} { 775 lappend list $var $response($var) 776 } 777 } 778 779 lappend list page $p 780 append html [document $list] 781 append html "\">$n</A>" 782 lappend navbar $html 783 } 784 } 785 786 if {"$end" == "Bottom"} { 787 puts "<BR/>" 788 } 789 790 set class [get_css_class TABLE DIONavButtons ${pref}NavButtons] 791 puts "<TABLE WIDTH=\"100%\" CLASS=\"$class\">" 792 puts "<TR CLASS=\"$class\">" 793 puts "<TD CLASS=\"$class\">" 794 puts "<FONT SIZE=-1>" 795 796 if {"$end" == "Top"} { 797 puts "$count records; page:" 798 } else { 799 puts "Go to page" 800 } 801 802 foreach link $navbar { 803 puts "$link " 804 } 805 806 puts "</FONT>" 807 puts "</TD>" 808 809 if {"$end" == "Top" && $pages>10} { 810 set f [::form #auto] 811 $f start -method get 812 hide_hidden_vars $f 813 hide_selection $f 814 hide_cgi_vars $f 815 puts "<TD ALIGN=RIGHT>" 816 puts "<FONT SIZE=-1>" 817 puts "Jump to" 818 $f text page -size 4 -value $response(page) 819 $f submit submit -value "Go" 820 puts "</FONT>" 821 puts "</TD>" 822 $f end 823 $f destroy 824 } 825 puts "</TR>" 826 puts "</TABLE>" 827 if {"$end" == "Top"} { 828 puts "<BR/>" 829 } 830 } 831 832 833 method rowheader {{total 0}} { 834 set fieldList $fields 835 if {![lempty $rowfields]} { 836 set fieldList $rowfields 837 } 838 839 set rowcount 0 840 841 puts "<P>" 842 843 if {$topnav} { 844 page_buttons Top $total 845 } 846 847 puts {<TABLE BORDER WIDTH="100%" CLASS="DIORowHeader">} 848 puts {<TR CLASS="DIORowHeader">} 849 set W [expr {100 / [llength $fieldList]}] 850 851 foreach field $fieldList { 852 set name [$field name] 853 set text [$field text] 854 855 regsub -all $labelsplit $text "<BR>" text 856 set col_title "" 857 set col_title_text $text 858 859 if [info exists hovertext($name)] { 860 set col_title " title=\"$hovertext($name)\"" 861 set col_title_text "<span$col_title>$text</span>" 862 } 863 864 if {![sortable $name]} { 865 set html $col_title_text 866 } else { 867 set html "" 868 set list {} 869 870 foreach var {mode query by how num} { 871 if {[info exists response($var)]} { 872 lappend list $var $response($var) 873 set sep "&" 874 } 875 } 876 877 lappend list sort $name 878 set a_attr "" 879 880 if {[info exists response(sort)] && "$response(sort)" == "$name"} { 881 set rev 1 882 if {[info exists response(rev)]} { 883 set rev [expr {1 - $response(rev)}] 884 } 885 886 lappend list rev $rev 887 append html "$col_title_text " 888 889 set desc $rev 890 if [info exists order($name)] { 891 switch -glob -- [string tolower $order($name)] { 892 desc* { 893 set desc [expr {1 - $desc}] 894 } 895 } 896 } 897 898 set text [lindex $arrows $desc] 899 set a_attr { class="DIOArrow"} 900 } 901 append html "<A HREF=\"" 902 append html [document $list] 903 append html "\"$a_attr$col_title>$text</A>" 904 } 905 set class [get_css_class TH DIORowHeader DIORowHeader-$name] 906 puts "<TH CLASS=\"$class\" WIDTH=\"$W%\">$html</TH>" 907 } 908 909 if {![lempty $rowfunctions] && "$rowfunctions" != "-"} { 910 puts {<TH CLASS="DIORowHeaderFunctions" WIDTH="0%"> </TH>} 911 } 912 puts "</TR>" 913 } 914 915 private method altrow {} { 916 incr rowcount 917 if !$alternaterows { return "" } 918 if {$rowcount % 2} { return "" } 919 return Alt 920 } 921 922 method showrow {arrayName} { 923 upvar 1 $arrayName a 924 925 set alt [altrow] 926 927 set fieldList $fields 928 if {![lempty $rowfields]} { 929 set fieldList $rowfields 930 } 931 932 puts "<TR CLASS=\"DIORowField$alt\">" 933 foreach field $fieldList { 934 set name [$field name] 935 set column $name 936 937 if [info exists alias($name)] { 938 set column $alias($name) 939 } 940 941 set class [get_css_class TD DIORowField$alt DIORowField$alt-$name] 942 943 set text [column_value $name a] 944 945 if ![string length $text] { 946 set text " " 947 } 948 949 set attr NOWRAP 950 if [info exists attributes($name)] { 951 append attr " $attributes($name) " 952 if [regsub -nocase { +wrap +} " $attr " { } attr] { 953 set attr $attributes($name) 954 } 955 set attr [string trim $attr] 956 } 957 puts "<TD CLASS=\"$class\" $attr>$text</TD>" 958 } 959 960 if {![lempty $rowfunctions] && "$rowfunctions" != "-"} { 961 set f [::form #auto] 962 $f start -method get 963 puts "<TD NOWRAP CLASS=\"DIORowFunctions$alt\">" 964 hide_hidden_vars $f 965 hide_selection $f 966 $f hidden query -value [escape_cgi [makekey a]] 967 $f hidden by -value [escape_cgi $key] 968 969 if {[llength $rowfunctions] > 2} { 970 $f select mode -values $rowfunctions -class DIORowFunctionSelect$alt 971 $f submit submit -value "Go" -class DIORowFunctionButton$alt 972 } else { 973 foreach func $rowfunctions { 974 $f submit mode -value $func -class DIORowFunctionButton$alt 975 } 976 } 977 puts "</TD>" 978 $f end 979 $f destroy 980 } 981 982 puts "</TR>" 983 } 984 985 method rowfooter {{total 0}} { 986 if [array exists lastrow] { 987 set rowclass "DIORowField[altrow]" 988 989 set fieldList $fields 990 if {![lempty $rowfields]} { set fieldList $rowfields } 991 992 set skip 0 993 set row {} 994 995 foreach field $fieldList { 996 set name [$field name] 997 if [info exists lastrow($name)] { 998 if {$skip > 0} { 999 lappend row "<TD CLASS=\"$rowclass\" span=\"$skip\"> </TD>" 1000 } 1001 set skip 0 1002 lappend row "<TD CLASS=\"$rowclass\">$lastrow($name)</TD>" 1003 } else { 1004 incr skip 1005 } 1006 } 1007 1008 if [llength $row] { 1009 if {![lempty $rowfunctions] && "$rowfunctions" != "-"} { 1010 incr skip 1011 } 1012 1013 if {$skip > 0} { 1014 lappend row "<TD CLASS=\"$rowclass\" span=\"$skip\"> </TD>" 1015 } 1016 1017 puts "<TR CLASS=\"rowclass\">" 1018 puts [join $row " "] 1019 puts "</TR>" 1020 } 1021 } 1022 puts "</TABLE>" 1023 1024 if {$bottomnav} { 1025 page_buttons Bottom $total 1026 } 1027 } 1028 1029 ## Check field's "sortability" 1030 protected method sortable {name} { 1031 ## If allowsort is false, nothing is sortable. 1032 if !$allowsort { 1033 return 0 1034 } 1035 1036 ## If there's a list of sortfields, it's only sortable if it's in that 1037 if {![lempty $sortfields]} { 1038 if {[lsearch $sortfields $name] < 0} { 1039 return 0 1040 } 1041 } 1042 1043 ## Otherwise if it's searchable, it's sortable 1044 return [searchable $name] 1045 } 1046 1047 ## Check field's "searchability" 1048 protected method searchable {name} { 1049 ## If it's marked as searchable 1050 if {[lsearch $searchfields $name] < 0} { 1051 return 1 1052 } 1053 1054 ## If it's filtered and the filter isn't reversible one way or another 1055 if { 1056 [info exists filters($name)] && 1057 ![info exists unfilters($name)] && 1058 ![string match "*_ok" $filters($name)] 1059 } { 1060 return 0 1061 } 1062 1063 ## If it's an alias field 1064 if [info exists alias($name)] { 1065 return 0 1066 } 1067 1068 # Otherwise it's searchable 1069 return 1 1070 } 1071 1072 ## Define a new function. 1073 method function {name} { 1074 lappend allfunctions $name 1075 } 1076 1077 ## Define a field in the object. 1078 method field {name args} { 1079 import_keyvalue_pairs data $args 1080 1081 set class STDisplayField 1082 if {[info exists data(type)]} { 1083 if {![lempty [::itcl::find classes *STDisplayField_$data(type)]]} { 1084 set class STDisplayField_$data(type) 1085 } 1086 } 1087 1088 set field [ 1089 eval [ 1090 list $class #auto -name $name -display $this -form $form 1091 ] $args 1092 ] 1093 lappend fields $field 1094 lappend allfields $field 1095 lappend allnames $name 1096 1097 set FieldNameMap($name) $field 1098 set NameTextMap([$field text]) $name 1099 } 1100 1101 private method make_limit_selector {values _selector {_array ""}} { 1102 if ![info exists limit] { return 0 } 1103 1104 upvar 1 $_selector selector 1105 if {"$_array" != ""} { 1106 upvar 1 $_array array 1107 } 1108 1109 foreach val $values name $keyfields { 1110 lappend selector [list = $name $val] 1111 } 1112 1113 foreach {k v} $limit { 1114 regsub {^-} $k "" k 1115 lappend selector [list = $k $v] 1116 set array($k) $v 1117 } 1118 1119 return 1 1120 } 1121 1122 # Simplify a "compare" operation in a search to make it compatible with 1123 # standard ctables 1124 method simplify_compare {_compare} { 1125 upvar 1 $_compare compare 1126 1127 set new_compare {} 1128 set changed 0 1129 foreach list $compare { 1130 set op [lindex $list 0] 1131 1132 if {"$op" == "<>"} { 1133 set list [concat {!=} [lrange $list 1 end]] 1134 set changed 1 1135 } elseif {[regexp {^(-?)(.)match} $op _ not ch]} { 1136 set op [lindex {match notmatch} [string length $not]] 1137 unset -nocomplain fn 1138 switch -exact -- [string tolower $ch] { 1139 u { append op _case; set fn toupper } 1140 l { append op _case; set fn tolower } 1141 x { append op _case } 1142 } 1143 1144 set pat [lindex $list 2] 1145 if [info exists fn] { 1146 set pat [string $fn $pat] 1147 } 1148 1149 set list [concat $op [lindex $list 1] [list $pat]] 1150 set changed 1 1151 } 1152 lappend new_compare $list 1153 } 1154 1155 if {$changed} { 1156 set compare $new_compare 1157 } 1158 } 1159 1160 # Perform an extended "search" request bundled in an array 1161 method perform {_request args} { 1162 upvar 1 $_request request 1163 array set search [array get request] 1164 array set search $args 1165 uplevel 1 [list $table search] [array get search] 1166 } 1167 1168 method fetch {keyVal arrayName} { 1169 upvar 1 $arrayName array 1170 if [make_limit_selector $keyVal selector] { 1171 set result [$table search -compare $selector -array_with_nulls array] 1172 } else { 1173 set list [$table array_get_with_nulls $keyVal] 1174 set result [llength $list] 1175 if {$result} { 1176 array set array $list 1177 } 1178 } 1179 return $result 1180 } 1181 1182 # SHorthand to make a key from table 1183 method makekey {arrayName} { 1184 upvar 1 $arrayName array 1185 1186 set list {} 1187 foreach kf $keyfields { 1188 if [info exists array($kf)] { 1189 lappend list $array($kf) 1190 } 1191 } 1192 1193 if {[llength $list] == 0} { 1194 if [info exists array(_key)] { 1195 return $array(_key) 1196 } else { 1197 return -code error "No key in array" 1198 } 1199 } 1200 1201 if {[llength $list] == 1} { 1202 return [lindex $list 0] 1203 } 1204 1205 return $list 1206 } 1207 1208 # SHorthand to store table 1209 method store {arrayName} { 1210 upvar 1 $arrayName array 1211 if [make_limit_selector {} selector array] { 1212 if ![$table search -compare $selector -key _] { 1213 return 0 1214 } 1215 } 1216 return [$table store [array get array]] 1217 } 1218 1219 method delete {keyVal} { 1220 if [make_limit_selector $keyVal selector] { 1221 if ![$table search -compare $selector -key keyVal] { 1222 return 0 1223 } 1224 } 1225 return [$table delete $keyVal] 1226 } 1227 1228 method pretty_fields {list} { 1229 set labels {} 1230 foreach field $list { 1231 lappend labels [$field text] 1232 } 1233 return $labels 1234 } 1235 1236 method set_field_values {arrayName} { 1237 upvar 1 $arrayName array 1238 1239 # for all the elements in the specified array, try to invoke 1240 # the field for that name, invoking the method "value" to 1241 # set the value to the specified value 1242 foreach name [array names array] { 1243 if [info exists FieldNameMap($name)] { 1244 $FieldNameMap($name) configure -value $array($name) 1245 } 1246 } 1247 } 1248 1249 method get_field_values {arrayName} { 1250 upvar 1 $arrayName array 1251 1252 foreach field $allfields { 1253 set v [$field value] 1254 set n [$field name] 1255 if {"$v" == "" && [info exists blankval($n)]} { 1256 if {"$blankval($n)" != "$v"} continue 1257 } 1258 set array($n) $v 1259 } 1260 } 1261 1262 method make_request {_request} { 1263 upvar 1 $_request request 1264 unset -nocomplain request 1265 array unset request 1266 } 1267 1268 method set_limit {_request {selector {}}} { 1269 upvar 1 $_request request 1270 1271 if [info exists request(-compare)] { 1272 set request(-compare) [concat $request(-compare) $selector] 1273 } else { 1274 set request(-compare) $selector 1275 } 1276 1277 make_limit_selector {} request(-compare) 1278 if [llength $request(-compare)] { 1279 return 1 1280 } 1281 1282 unset request(-compare) 1283 return 0 1284 } 1285 1286 method set_order {_request} { 1287 upvar 1 $_request request 1288 1289 if {"[set sort [request_to_sort]]" != ""} { 1290 set request(-sort) $sort 1291 } 1292 } 1293 1294 method set_page {_request} { 1295 upvar 1 $_request request 1296 1297 set recno [get_offset] 1298 if {$recno > 0} { 1299 set request(-offset) $recno 1300 } 1301 1302 if {$pagesize > 0} { 1303 set request(-limit) $pagesize 1304 } 1305 } 1306 1307 method gencsvfile {selector} { 1308 if {"$csvfile" == ""} { 1309 return 1310 } 1311 1312 ::stapi::display::load_csv 1313 1314 make_request request 1315 set_limit request 1316 set_order request 1317 1318 if [catch {set fp [open $csvfile w]} err] { 1319 $r destroy 1320 return 1321 } 1322 1323 set columns {} 1324 1325 foreach field $fields { 1326 set name [$field name] 1327 # Don't put alias fields in unless there's a csv filter for them 1328 if [info exists alias($name)] { 1329 if ![info exists csvfilters($name)] { 1330 continue 1331 } 1332 } 1333 lappend columns $name 1334 set label [$field text] 1335 regsub -all { *<[^>]*> *} $label " " label 1336 lappend textlist $label 1337 } 1338 1339 if [info exists textlist] { 1340 puts $fp [::csv::join $textlist] 1341 } 1342 1343 perform request -array_with_nulls a -key k -code { 1344 1345 # If there's no fields defined, then use the columns we got from 1346 # the query and put their names out as the first line 1347 1348 if {![llength $columns]} { 1349 set columns [array names a] 1350 puts $fp [::csv::join $columns] 1351 } 1352 set list {} 1353 foreach name $columns { 1354 lappend list [column_value $name a csv] 1355 } 1356 puts $fp [::csv::join $list] 1357 } 1358 1359 close $fp 1360 1361 $r destroy 1362 } 1363 1364 method showcsvform {query} { 1365 $form start -method get 1366 puts "<TR CLASS='DIOForm'><TD CLASS='DIOForm' VALIGN='MIDDLE' WIDTH='100%'>" 1367 # save hidden vars 1368 hide_hidden_vars $form 1369 1370 # save form vars so state isn't lost 1371 foreach {n v} [state] { 1372 $form hidden $n -value [escape_cgi $v] 1373 } 1374 1375 # save search 1376 hide_selection $form 1377 # save query for generation 1378 $form hidden ct_csv -value [escape_cgi $query] 1379 1380 if $csvredirect { 1381 set csvlabel "Download CSV file" 1382 } else { 1383 set csvlabel "Generate CSV file" 1384 } 1385 1386 $form submit submit -value $csvlabel \ 1387 -class DIOMainSubmitButton 1388 1389 if [file exists $csvfile] { 1390 if ![catch {file stat $csvfile st}] { 1391 if $csvredirect { 1392 puts "Previous: " 1393 } 1394 set filename $csvfile 1395 regsub {^.*/} $filename "" filename 1396 puts "<A HREF=\"$csvurl\">$filename</A>:" 1397 puts "$st(size) bytes," 1398 puts [clock format $st(mtime) -format "%d-%b-%Y %H:%M:%S"] 1399 } 1400 } 1401 1402 puts "</TD></TR>" 1403 $form end 1404 } 1405 1406 method DisplayRequest {selector} { 1407 make_request request 1408 set partial [set_limit request $selector] 1409 1410 if {!$partial} { 1411 if {$rows} { 1412 set total $rows 1413 } else { 1414 set total [$table count] 1415 } 1416 } else { 1417 set total [perform request] 1418 } 1419 1420 if {$total <= [get_offset]} { 1421 puts "Could not find any matching records." 1422 return 1423 } 1424 1425 rowheader $total 1426 1427 set_order request 1428 set_page request 1429 perform request -array_with_nulls a -code { showrow a } 1430 1431 rowfooter $total 1432 1433 if {"$csvfile" != "" && "$csvurl" != ""} { 1434 showcsvform $query 1435 } 1436 } 1437 1438 method Main {} { 1439 puts "<TABLE BORDER='0' WIDTH='100%' CLASS='DIOForm'>" 1440 1441 display_selection {" "} {} 1442 1443 set skipfunctions {} 1444 if {[lsearch $functions Search] >= 0} { 1445 foreach f "Edit Delete" { 1446 if {[lsearch $functions $f] >= 0 1447 && [lsearch $rowfunctions $f] >= 0} { 1448 lappend skipfunctions $f 1449 } 1450 } 1451 } 1452 1453 puts "<TR CLASS='DIOForm'>" 1454 puts "<TD CLASS='DIOForm' ALIGN='LEFT' VALIGN='MIDDLE' WIDTH='1%' NOWRAP>" 1455 1456 set selfunctions {} 1457 foreach f $functions { 1458 if {"$f" != "List"} { 1459 if {[lsearch $skipfunctions $f] < 0} { 1460 lappend selfunctions $f 1461 } 1462 } else { 1463 set listform [::form #auto] 1464 puts "<DIV STYLE='display:none'>" 1465 $listform start -method get 1466 puts "</DIV>" 1467 hide_hidden_vars $listform 1468 # hide_selection $listform 1469 $listform hidden mode -value "List" 1470 $listform hidden query -value "" 1471 $listform submit submit -value "Show All" \ 1472 -class DIORowFunctionButton 1473 puts "<DIV STYLE='display:none'>" 1474 $listform end 1475 $listform destroy 1476 puts "</DIV>" 1477 } 1478 } 1479 puts "</TD>" 1480 puts "<TD CLASS='DIOForm' ALIGN='LEFT' VALIGN='MIDDLE' WIDTH='1%' NOWRAP>" 1481 1482 puts "<DIV STYLE='display:none'>" 1483 $form start -method get 1484 puts "</DIV>" 1485 puts " " 1486 1487 hide_hidden_vars $form 1488 hide_selection $form 1489 1490 if {[llength $selfunctions] > 2} { 1491 $form select mode -values $selfunctions -class DIOMainFunctionsSelect 1492 puts "where" 1493 } else { 1494 puts "Select:" 1495 } 1496 1497 set fieldList $fields 1498 if {![lempty $searchfields]} { set fieldList $searchfields } 1499 1500 set first "-column-" 1501 if [info exists response(by)] { 1502 set first $response(by) 1503 if {"$first" == "_key"} { 1504 set first "-key-" 1505 } 1506 } 1507 1508 set labels [list $first] 1509 foreach field $fieldList { 1510 if ![searchable [$field name]] { continue } 1511 set label [$field text] 1512 if {"$label" != "$first"} { 1513 lappend labels $label 1514 } 1515 } 1516 1517 $form select by -values $labels -class DIOMainSearchBy 1518 1519 puts "</TD>" 1520 puts "<TD CLASS='DIOForm' ALIGN='LEFT' VALIGN='MIDDLE' WIDTH='1%' NOWRAP>" 1521 if [string match {*[Ss]earch*} $selfunctions] { 1522 $form select how -values {"=" "<" "<=" ">" ">=" "<>"} 1523 } else { 1524 puts "is" 1525 } 1526 1527 puts "</TD>" 1528 puts "<TD CLASS='DIOForm' ALIGN='LEFT' VALIGN='MIDDLE' WIDTH='1%' NOWRAP>" 1529 if [info exists response(query)] { 1530 $form text query -value [escape_cgi $response(query)] -class DIOMainQuery 1531 } else { 1532 $form text query -value "" -class DIOMainQuery 1533 } 1534 1535 puts "</TD>" 1536 puts "<TD CLASS='DIOForm' ALIGN='LEFT' VALIGN='MIDDLE' WIDTH='100%' NOWRAP>" 1537 1538 if [string match {*[sS]earch*} $selfunctions] { 1539 display_add_button $form 1540 } 1541 1542 if {[llength $selfunctions] > 2} { 1543 $form submit submit -value "GO" -class DIOMainSubmitButton 1544 } else { 1545 foreach f $selfunctions { 1546 $form submit mode -value $f -class DIOMainSubmitButton 1547 } 1548 } 1549 1550 if {![lempty $numresults]} { 1551 puts "</TD></TR>" 1552 puts "<TR CLASS='DIOForm'><TD CLASS='DIOForm'>Results per page: " 1553 $form select num -values $numresults -class DIOMainNumResults 1554 } 1555 1556 puts "</TD></TR>" 1557 1558 puts "<DIV STYLE='display:none'>" 1559 $form end 1560 puts "</DIV>" 1561 puts "</TABLE>" 1562 } 1563 1564 protected method parse_order {name list {reverse 0}} { 1565 set descending 0 1566 foreach word $list { 1567 if [info exists nextvar] { 1568 set $nextvar $word 1569 unset nextvar 1570 continue 1571 } 1572 1573 switch -glob -- $word { 1574 asc* { set descending 0 } 1575 desc* { set descending 1 } 1576 null* { set nextvar null } 1577 } 1578 } 1579 1580 # ctables doesn't handle this yet 1581 # if [info exists null] { 1582 # set field "COALESCE($field,$null)" 1583 # } 1584 1585 if {$reverse} { 1586 set descending [expr {1 - $descending}] 1587 } 1588 if $descending { 1589 set name -$name 1590 } 1591 1592 return $name 1593 } 1594 1595 method request_to_sort {} { 1596 if {[info exists response(sort)] && ![lempty $response(sort)]} { 1597 set name [CName $response(sort) 0] 1598 if {"$name" == ""} { 1599 unset response(sort) 1600 } else { 1601 set rev 0 1602 if {[info exists response(rev)] && $response(rev)} { 1603 set rev 1 1604 } 1605 1606 set ord ascending 1607 if [info exists order($name)] { 1608 set ord $order($name) 1609 } 1610 1611 return [list [parse_order $name $ord $rev]] 1612 } 1613 } 1614 1615 if {"$defaultsortfield" != ""} { 1616 if [regexp {^-(.*)} $defaultsortfield _ name] { 1617 set ord descending 1618 } else { 1619 set ord ascending 1620 set name $defaultsortfield 1621 } 1622 return [list [parse_order [CName $name] $ord]] 1623 } 1624 1625 return {} 1626 } 1627 1628 method get_offset {} { 1629 if {$pagesize <= 0} { return 0 } 1630 if {![info exists response(page)]} { return 0 } 1631 return [expr {($response(page) - 1) * $pagesize}] 1632 } 1633 1634 protected method display_selection {precells postcells} { 1635 set selection [get_selection 0] 1636 if {![llength $selection]} { 1637 return 1638 } 1639 set span [expr {4 + [llength $precells] + [llength $postcells]}] 1640 puts "<TR><TD CLASS='DIOFormHeader' COLSPAN='$span'>" 1641 puts {<font color="#444444"><b>Current filters:</b></font>} 1642 puts {</TD></TR>} 1643 foreach search $selection { 1644 foreach {how col what} $search { break } 1645 puts {<TR CLASS="DIOSelect">} 1646 set f [::form #auto] 1647 puts {<DIV STYLE="display:none">} 1648 $f start -method get 1649 puts "</DIV>" 1650 hide_hidden_vars $f 1651 hide_cgi_vars $f mode 1652 hide_selection $f - $search 1653 if {[string match "*-*match*" $how]} { 1654 set how "is not like" 1655 } elseif {[string match "*match*" $how]} { 1656 set how "is like" 1657 } 1658 1659 foreach cell $precells { 1660 puts "<TD CLASS='DIOSelect' WIDTH='1%'>$cell</TD>" 1661 } 1662 foreach \ 1663 cell [list [DName $col] $how $what] \ 1664 align {right middle left} \ 1665 { 1666 puts "<TD CLASS='DIOSelect' ALIGN='$align' WIDTH='1%'>[escape_cgi $cell]</TD>" 1667 } 1668 puts {<TD CLASS="DIOSelect" WIDTH="100%" ALIGN="LEFT">} 1669 $f submit mode -value "-" -class DIOForm 1670 puts "</TD>" 1671 foreach cell $postcells { 1672 puts "<TD CLASS='DIOSelect'>$cell</TD>" 1673 } 1674 puts "<DIV STYLE='display:none'>" 1675 $f end 1676 $f destroy 1677 puts "</DIV>" 1678 puts "</TR>" 1679 } 1680 puts "<TR><TD CLASS='DIOFormHeader' COLSPAN='$span'>" 1681 puts "" 1682 puts "</TD></TR>" 1683 } 1684 1685 protected method display_add_button {f} { 1686 $f submit mode -value "+" -class DIOForm 1687 } 1688 1689 protected method add_search_to_selection {} { 1690 set search_list [get_selection 1] 1691 array unset ct_selection 1692 } 1693 1694 protected method get_selection {searching} { 1695 if [info exists ct_selection($searching)] { 1696 return $ct_selection($searching) 1697 } 1698 1699 if ![info exists search_list] { 1700 if [info exists response(ct_sel)] { 1701 set search_list $response(ct_sel) 1702 if {[llength $search_list] == 3 1703 && [llength [lindex $search_list 0]] == 1} { 1704 set search_list [list $search_list] 1705 } 1706 } else { 1707 set search_list {} 1708 } 1709 } 1710 set ct_selection(0) $search_list 1711 if !$searching { 1712 return $search_list 1713 } 1714 set new_list $search_list 1715 1716 if [info exists response(by)] { 1717 set name [CName $response(by)] 1718 1719 set what $response(query) 1720 1721 set how "=" 1722 if {[info exists response(how)] && [string length $response(how)]} { 1723 set how $response(how) 1724 } 1725 1726 if {[string match {*[*?]*} $what]} { 1727 if {"$how" == "="} { 1728 set how "match" 1729 } elseif {"$how" == "<>"} { 1730 set how "notmatch" 1731 } 1732 } 1733 if {[string match "*like*" $how] || [string match "*match*" $how]} { 1734 switch -glob -- $how { 1735 *not* { set how "notmatch" } 1736 -* { set how "match-" } 1737 default { set how "match" } 1738 } 1739 if {[info exists case($name)]} { 1740 switch -glob -- [string tolower $case(name)] { 1741 u* { 1742 set what [string toupper $what] 1743 append how "_case" 1744 } 1745 l* { 1746 set what [string tolower $what] 1747 append how "_case" 1748 } 1749 x* { 1750 append how "_case" 1751 } 1752 } 1753 } 1754 } 1755 1756 if {"$how" == "<>"} { 1757 set how "!=" 1758 } 1759 1760 set search [list $how $name $what] 1761 if {[lsearch $new_list $search] < 0} { 1762 lappend new_list [list $how $name $what] 1763 } 1764 } 1765 set ct_selection(1) $new_list 1766 return $new_list 1767 } 1768 1769 method Search {} { 1770 display_request_with_selection [get_selection 1] 1771 } 1772 1773 method List {} { 1774 display_request_with_selection [get_selection 0] 1775 } 1776 1777 protected method display_request_with_selection {selection} { 1778 set request {} 1779 foreach target $selection { 1780 foreach {how column what} $target { break } 1781 1782 if {[info exists unfilters($column)] && "$unfilters($column)" != "-"} { 1783 set what [$unfilters($column) $what] 1784 } 1785 1786 lappend request [list $how $column $what] 1787 } 1788 DisplayRequest $request 1789 } 1790 1791 method Add {} { 1792 showform 1793 } 1794 1795 method Edit {} { 1796 if {![fetch $response(query) array]} { 1797 puts "That record does not exist in the database." 1798 return 1799 } 1800 1801 set_field_values array 1802 1803 showform 1804 } 1805 1806 ## 1807 ## When we save, we want to set all the fields' values and then get 1808 ## them into a new array. We do this because we want to clean any 1809 ## unwanted variables out of the array but also because some fields 1810 ## have special handling for their values, and we want to make sure 1811 ## we get the right value. 1812 ## 1813 method Save {} { 1814 if {[info exists response(cancel.x)]} { 1815 Cancel 1816 return 1817 } 1818 1819 ## We need to see if the key exists. If they are adding a new 1820 ## entry, we just want to see if the key exists. If they are 1821 ## editing an entry, we need to see if they changed the keyfield 1822 ## while editing. If they didn't change the keyfield, there's no 1823 ## reason to check it. 1824 set adding [expr {$response(DIODfromMode) == "Add"}] 1825 if {$adding} { 1826 set keyVal [makekey response] 1827 set list [$table array_get_with_nulls $keyVal] 1828 if {[llength $list]} { 1829 array set a $list 1830 } 1831 } else { 1832 set keyVal $response(DIODkey) 1833 set newkey [makekey response] 1834 1835 ## If we have a new key, and the newkey doesn't exist in the 1836 ## database, we are moving this record to a new key, so we 1837 ## need to delete the old key. 1838 if {$keyVal != $newkey} { 1839 if {![fetch $newkey a]} { 1840 delete $keyVal 1841 } 1842 } 1843 } 1844 1845 if {[array exists a]} { 1846 puts "That record ($keyVal) already exists in the database." 1847 return 1848 } 1849 1850 set_field_values response 1851 get_field_values storeArray 1852 1853 # Don't try and write readonly values. 1854 foreach name [array names storeArray] { 1855 if [[FName $name] readonly] { 1856 unset storeArray($name) 1857 } 1858 } 1859 1860 # Because an empty string is not EXACTLY a null value and not always 1861 # a legal value, if the array element is empty and we're adding a 1862 # new row or there is no legal null value for the type 1863 # remove it from the array -- PdS Jul 2006 1864 foreach {n v} [array get storeArray] { 1865 if {"$v" == ""} { 1866 if $adding { 1867 unset storeArray($n) 1868 } elseif {![info exists FieldNameMap($n)]} { 1869 unset storeArray($n) 1870 } elseif {![$FieldNameMap($n) null_ok]} { 1871 unset storeArray($n) 1872 } 1873 } 1874 } 1875 1876 store storeArray 1877 headers redirect [document] 1878 } 1879 1880 # return a URL containing all of the current state 1881 protected method document {{extra {}}} { 1882 set url $document 1883 set ch "?" 1884 foreach {n v} $extra { 1885 append url $ch $n = [escape_url $v] 1886 set ch "&" 1887 } 1888 foreach {n v} [array get hidden] { 1889 append url $ch $n = [escape_url $v] 1890 set ch "&" 1891 } 1892 set selection [get_selection 0] 1893 if [llength $selection] { 1894 append url $ch ct_sel = [escape_url $selection] 1895 set ch "&" 1896 } 1897 return $url 1898 } 1899 1900 method Delete {} { 1901 if {![fetch $response(query) array]} { 1902 puts "That record does not exist in the database." 1903 return 1904 } 1905 1906 if {!$confirmdelete} { 1907 DoDelete 1908 return 1909 } 1910 1911 puts "<CENTER>" 1912 puts {<TABLE CLASS="DIODeleteConfirm">} 1913 puts "<TR CLASS='DIODeleteConfirm'>" 1914 puts {<TD COLSPAN=2 CLASS="DIODeleteConfirm">} 1915 puts "Are you sure you want to delete this record from the database?" 1916 puts "</TD>" 1917 puts "</TR>" 1918 puts "<TR CLASS='DIODeleteConfirmYesButton'>" 1919 puts {<TD ALIGN="center" CLASS="DIODeleteConfirmYesButton">} 1920 set f [::form #auto] 1921 $f start -method post 1922 hide_hidden_vars $f 1923 hide_selection $f 1924 $f hidden mode -value DoDelete 1925 $f hidden query -value [escape_cgi $response(query)] 1926 $f submit submit -value Yes -class DIODeleteConfirmYesButton 1927 $f end 1928 $f destroy 1929 puts "</TD>" 1930 puts {<TD ALIGN="center" CLASS="DIODeleteConfirmNoButton">} 1931 set f [::form #auto] 1932 $f start -method post 1933 hide_hidden_vars $f 1934 hide_selection $f 1935 $f submit submit -value No -class "DIODeleteConfirmNoButton" 1936 $f end 1937 $f destroy 1938 puts "</TD>" 1939 puts "</TR>" 1940 puts "</TABLE>" 1941 puts "</CENTER>" 1942 } 1943 1944 method DoDelete {} { 1945 if [catch {delete $response(query)} err] { 1946 error "delete $response(query) => $err" $::errorInfo 1947 } 1948 1949 headers redirect [document] 1950 } 1951 1952 method Details {} { 1953 if {![fetch $response(query) array]} { 1954 puts "That record does not exist in the database." 1955 return 1956 } 1957 1958 set_field_values array 1959 1960 showview 1961 } 1962 1963 method Cancel {} { 1964 headers redirect [document] 1965 } 1966 1967 ### 1968 ## Define variable functions for each variable. 1969 ### 1970 1971 private method names2fields {nameList} { 1972 set fieldList {} 1973 foreach name $nameList { 1974 lappend fieldList [FName $name] 1975 } 1976 return $fieldList 1977 } 1978 1979 protected method fields2names {fieldList} { 1980 set nameList {} 1981 foreach field $fieldList { 1982 lappend nameList [$field name] 1983 } 1984 return $nameList 1985 } 1986 1987 method fields {{list ""}} { 1988 if {[lempty $list]} { return [fields2names $fields] } 1989 set fields [names2fields $list] 1990 } 1991 1992 method searchfields {{list ""}} { 1993 if {[lempty $list]} { return [fields2names $searchfields] } 1994 set searchfields [names2fields $list] 1995 } 1996 1997 method rowfields {{list ""}} { 1998 if {[lempty $list]} { return [fields2names $rowfields] } 1999 set rowfields [names2fields $list] 2000 } 2001 2002 method lastrow {name {value ""}} { 2003 if [string length $value] { 2004 set lastrow($name) $value 2005 } elseif {[info exists lastrow($name)]} { 2006 set value $lastrow($name) 2007 } 2008 return $value 2009 } 2010 2011 method alias {name {value ""}} { 2012 if [string length $value] { 2013 set alias($name) $value 2014 } elseif {[info exists alias($name)]} { 2015 set value $alias($name) 2016 } else { 2017 set value $name 2018 } 2019 return $value 2020 } 2021 2022 protected method column_value {name _row {type ""}} { 2023 upvar 1 $_row row 2024 2025 set val "" 2026 2027 set column $name 2028 if [info exists alias($name)] { 2029 set column $alias($name) 2030 } 2031 2032 if [info exists row($column)] { 2033 set val [apply_filter $name [escape_html $row($column)] row $type] 2034 } 2035 2036 return $val 2037 } 2038 2039 method apply_filter {name val {_row ""} {which ""}} { 2040 if [info exists ${which}filters($name)] { 2041 set cmd [list [set ${which}filters($name)] $val] 2042 2043 if {"$_row" != "" && [info exists ${which}filtercols($name)]} { 2044 upvar 1 $_row row 2045 foreach n [set ${which}filtercols($name)] { 2046 if [info exists row($n)] { 2047 lappend cmd $row($n) 2048 } 2049 } 2050 } 2051 2052 set val [eval $cmd] 2053 } 2054 return $val 2055 } 2056 2057 method filter {name {value ""} args} { 2058 if [string length $value] { 2059 set f [uplevel 1 [list namespace which $value]] 2060 if {"$f" == ""} { 2061 return -code error "Unknown filter $value" 2062 } 2063 set value $f 2064 set filters($name) $f 2065 if [llength $args] { 2066 set filtercols($name) $args 2067 } 2068 } elseif {[info exists filters($name)]} { 2069 set value $filters($name) 2070 } 2071 return $value 2072 } 2073 2074 method smartfilter {args} { 2075 uplevel 1 [concat $this filter $args] 2076 } 2077 2078 method csvfilter {name {value ""} args} { 2079 if [string length $value] { 2080 set f [uplevel 1 [list namespace which $value]] 2081 if {"$f" == ""} { 2082 return -code error "Unknown filter $value" 2083 } 2084 set value $f 2085 set csvfilters($name) $f 2086 if [llength $args] { 2087 set csvfiltercols($name) $args 2088 } 2089 } elseif {[info exists csvfilters($name)]} { 2090 set value $csvfilters($name) 2091 } 2092 return $value 2093 } 2094 2095 method order {name {value ""}} { 2096 if [string length $value] { 2097 set order($name) $value 2098 } elseif {[info exists order($name)]} { 2099 set value $order($name) 2100 } 2101 return $value 2102 } 2103 2104 method hovertext {name {value ""}} { 2105 if [string length $value] { 2106 set hovertext($name) $value 2107 } elseif {[info exists hovertext($name)]} { 2108 set value $hovertext($name) 2109 } 2110 return $value 2111 } 2112 2113 method blankval {name {value ""}} { 2114 if [string length $value] { 2115 set blankval($name) $value 2116 } elseif {[info exists blankval($name)]} { 2117 set value $blankval($name) 2118 } 2119 return $value 2120 } 2121 2122 method limit {args} { 2123 if [string length $args] { 2124 set limit $args 2125 } elseif {[info exists limit]} { 2126 set args $limit 2127 } 2128 return $args 2129 } 2130 2131 method case {name {value ""}} { 2132 if [string length $value] { 2133 set case($name) $value 2134 } else { 2135 if [info exists case($name)] { 2136 set value $case($name) 2137 } 2138 } 2139 return $value 2140 } 2141 2142 method unfilter {name {value ""}} { 2143 if [string length $value] { 2144 if {"$value" != "-"} { 2145 set f [uplevel 1 [list namespace which $value]] 2146 if {"$f" == ""} { 2147 return -code error "Unknown filter $value" 2148 } 2149 set value $f 2150 } 2151 set unfilters($name) $value 2152 } elseif {[info exists unfilters($name)]} { 2153 set value $unfilters($name) 2154 } 2155 return $value 2156 } 2157 2158 method attributes {name {value ""}} { 2159 if [string length $value] { 2160 set attributes($name) $value 2161 } elseif {[info exists attributes($name)]} { 2162 set value $attributes($name) 2163 } 2164 return $value 2165 } 2166 2167 method hidden {name {value ""}} { 2168 if [string length $value] { 2169 set hidden($name) $value 2170 } elseif {[info exists hidden($name)]} { 2171 set value $hidden($name) 2172 } 2173 return $value 2174 } 2175 2176 method details {{string ""}} { configvar details $string } 2177 method readonly {{string ""}} { configvar readonly $string } 2178 method mode {{string ""}} { configvar mode $string } 2179 method csvfile {{string ""}} { configvar csvfile $string } 2180 2181 method title {{string ""}} { configvar title $string } 2182 method functions {{string "--"}} { configvar functions $string "--" } 2183 method pagesize {{string ""}} { configvar pagesize $string } 2184 method form {{string ""}} { configvar form $string } 2185 method cleanup {{string ""}} { configvar cleanup $string } 2186 method confirmdelete {{string ""}} { configvar confirmdelete $string } 2187 2188 method css {{string ""}} { configvar css $string } 2189 method persistentmain {{string ""}} { configvar persistentmain $string } 2190 method alternaterows {{string ""}} { configvar alternaterows $string } 2191 method allowsort {{string ""}} { configvar allowsort $string } 2192 method sortfields {{string ""}} { configvar sortfields $string } 2193 method topnav {{string "--"}} { configvar topnav $string "--" } 2194 method bottomnav {{string "--"}} { configvar bottomnav $string "--" } 2195 method numresults {{string ""}} { configvar numresults $string } 2196 method defaultsortfield {{string ""}} { configvar defaultsortfield $string } 2197 method labelsplit {{string ""}} { configvar labelsplit $string } 2198 2199 method rowfunctions {{string "--"}} { configvar rowfunctions $string "--" } 2200 method arrows {{string ""}} { configvar arrows $string } 2201 2202 method rows {{string 0}} { configvar rows $string 0 } 2203 2204 ## OPTIONS ## 2205 2206 public variable rows 0 2207 public variable title "" 2208 public variable fields "" 2209 public variable searchfields "" 2210 public variable functions "Search List Add Edit Delete Details" 2211 public variable pagesize 25 2212 public variable form "" 2213 public variable cleanup 1 2214 public variable confirmdelete 1 2215 public variable mode List 2216 public variable trap_errors 0 2217 2218 public variable css_file "" { 2219 if {![lempty $css_file]} { 2220 catch {unset cssArray} 2221 read_css_file 2222 } 2223 } 2224 2225 public variable css_files {"display.css" "diodisplay.css"} { 2226 if {![lempty $css_files]} { 2227 catch {unset cssArray} 2228 read_css_file 2229 } 2230 } 2231 2232 public variable persistentmain 1 2233 public variable alternaterows 1 2234 public variable allowsort 1 2235 public variable sortfields "" 2236 public variable topnav 1 2237 public variable bottomnav 1 2238 public variable numresults "" 2239 public variable defaultsortfield "" 2240 public variable labelsplit "\n" 2241 2242 protected variable rowfields "" 2243 public variable rowfunctions "Details Edit Delete" 2244 2245 public variable details 1 2246 public variable readonly 0 2247 2248 public variable response 2249 public variable cssArray 2250 public variable document "" 2251 protected variable allfields {} 2252 protected variable allnames {} 2253 protected variable NameTextMap 2254 protected variable FieldNameMap 2255 protected variable writefunctions { Add Edit Delete Save DoDelete } 2256 public variable allfunctions { 2257 Search 2258 List 2259 Add 2260 Edit 2261 Delete 2262 Details 2263 Main 2264 Save 2265 DoDelete 2266 Cancel 2267 } 2268 2269 # -csv, -csvfile, -csvurl 2270 # If -csvfile is provided and is in the same directory, gen -csvurl 2271 public variable csv 0 { 2272 if {$csv && "$csvfile" == ""} { 2273 set csvfile "download.csv" 2274 set csvurl "download.csv" 2275 } 2276 } 2277 public variable csvfile "" { 2278 set csv 1 2279 if {"$csvurl" == ""} { 2280 if ![regexp {^[.]*/} $csvfile] { 2281 set csvurl $csvfile 2282 } 2283 } 2284 } 2285 public variable csvurl "" 2286 public variable csvredirect 0 2287 2288 public variable arrows {"↓" "↑"} 2289 2290 private variable blankval 2291 private variable rowcount 2292 private variable filters 2293 private variable alias 2294 private variable lastrow 2295 private variable filtercols 2296 private variable hovertext 2297 private variable csvfilters 2298 private variable csvfiltercols 2299 private variable order 2300 private variable unfilters 2301 private variable attributes 2302 private variable hidden 2303 private variable limit 2304 private variable search_list 2305 2306} ; ## ::itcl::class STDisplay 2307 2308catch { ::itcl::delete class ::STDisplayField } 2309 2310# 2311# STDisplayField object -- defined for each field we're displaying 2312# 2313::itcl::class ::STDisplayField { 2314 2315 constructor {args} { 2316 ## We want to simulate Itcl's configure command, but we want to 2317 ## check for arguments that are not variables of our object. If 2318 ## they're not, we save them as arguments to the form when this 2319 ## field is displayed. 2320 import_keyvalue_pairs data $args 2321 foreach var [array names data] { 2322 if {![info exists $var]} { 2323 lappend formargs -$var $data($var) 2324 } else { 2325 set $var $data($var) 2326 } 2327 } 2328 2329 # if text (field description) isn't set, prettify the actual 2330 # field name and use that 2331 if {[lempty $text]} { set text [pretty [split $name _]] } 2332 } 2333 2334 destructor { 2335 2336 } 2337 2338 method destroy {} { 2339 ::itcl::delete object $this 2340 } 2341 2342 # 2343 # get_css_class - ask the parent DIODIsplay object to look up 2344 # a CSS class entry 2345 # 2346 method get_css_class {tag default class} { 2347 return [$display get_css_class $tag $default $class] 2348 } 2349 2350 # 2351 # get_css_tag -- set tag to select or textarea if type is select 2352 # or textarea, else to input 2353 # 2354 method get_css_tag {} { 2355 switch -- $type { 2356 "select" { 2357 set tag select 2358 } 2359 "textarea" { 2360 set tag textarea 2361 } 2362 default { 2363 set tag input 2364 } 2365 } 2366 } 2367 2368 # 2369 # pretty -- prettify a list of words by uppercasing the first letter 2370 # of each word 2371 # 2372 method pretty {string} { 2373 set words "" 2374 foreach w $string { 2375 lappend words \ 2376 [string toupper [string index $w 0]][string range $w 1 end] 2377 } 2378 return [join $words " "] 2379 } 2380 2381 # 2382 # configvar - a convenient helper for creating methods that can 2383 # set and fetch one of the object's variables 2384 # 2385 method configvar {varName string {defval ""}} { 2386 if {"$string" == "$defval"} { return [set $varName] } 2387 configure -$varName $string 2388 } 2389 2390 # 2391 # showview - emit a table row of either DIOViewRow, DIOViewRowAlt, 2392 # DIOViewRow-fieldname (this object's field name), or 2393 # DIOViewRowAlt-fieldname, a table data field of either 2394 # DIOViewHeader or DIOViewHeader-fieldname, and then a 2395 # value of class DIOViewField or DIOViewField-fieldname 2396 # 2397 method showview {{alt ""}} { 2398 set class [get_css_class TR DIOViewRow$alt DIOViewViewRow$alt-$name] 2399 puts "<TR CLASS=\"$class\">" 2400 2401 set class [get_css_class TD DIOViewHeader DIOViewHeader-$name] 2402 puts "<TD CLASS=\"$class\">$text:</TD>" 2403 2404 set class [get_css_class TD DIOViewField DIOViewField-$name] 2405 puts "<TD CLASS=\"$class\">$value</TD>" 2406 2407 puts "</TR>" 2408 } 2409 2410 # 2411 # showform -- like showview, creates a table row and table data, but 2412 # if readonly isn't set, emits a form field corresponding to the type 2413 # of this field 2414 # 2415 method showform {} { 2416 set class [get_css_class TD DIOFormHeader DIOFormHeader-$name] 2417 2418 puts "<TR CLASS=\"$class\">" 2419 puts "<TD CLASS=\"$class\">$text:</TD>" 2420 2421 set class [get_css_class TD DIOFormField DIOFormField-$name] 2422 puts "<TD CLASS=\"$class\">" 2423 if {$readonly} { 2424 puts "$value" 2425 } else { 2426 set tag [get_css_tag] 2427 set class [get_css_class $tag DIOFormField DIOFormField-$name] 2428 2429 set text $value 2430 regsub -all "\"" $text {\"} text 2431 if {$type == "select"} { 2432 $form select $name -values $values -class $class -value $text 2433 } else { 2434 eval $form $type $name -value [list $text] $formargs -class $class 2435 } 2436 } 2437 puts "</TD>" 2438 puts "</TR>" 2439 } 2440 2441 method null_ok {} { 2442 return [expr {"$type" == "text"}] 2443 } 2444 2445 # methods that give us method-based access to get and set the 2446 # object's variables... 2447 method display {{string ""}} { configvar display $string } 2448 method form {{string ""}} { configvar form $string } 2449 method formargs {{string ""}} { configvar formargs $string } 2450 method name {{string ""}} { configvar name $string } 2451 method text {{string ""}} { configvar text $string } 2452 method type {{string ""}} { configvar type $string } 2453 method value {{string ""}} { configvar value $string } 2454 method readonly {{string ""}} { configvar readonly $string } 2455 2456 public variable display "" 2457 public variable form "" 2458 public variable formargs "" 2459 2460 # values - for fields of type "select" only, the values that go in 2461 # the popdown (or whatever) selector 2462 public variable values "" 2463 2464 # name - the field name 2465 public variable name "" 2466 2467 # text - the description text for the field. if not specified, 2468 # it's constructed from a prettified version of the field name 2469 public variable text "" 2470 2471 # value - the default value of the field 2472 public variable value "" 2473 2474 # type - the data type of the field 2475 public variable type "text" 2476 2477 # readonly - if 1, we don't allow the value to be changed 2478 public variable readonly 0 2479 2480} ; ## ::itcl::class STDisplayField 2481 2482catch { ::itcl::delete class ::STDisplayField_boolean } 2483 2484# 2485# STDisplayField_boolen -- superclass of STDisplayField that overrides 2486# a few methods to specially handle booleans 2487# 2488::itcl::class ::STDisplayField_boolean { 2489 inherit ::STDisplayField 2490 2491 constructor {args} {eval configure $args} { 2492 eval configure $args 2493 } 2494 2495 method add_true_value {string} { 2496 lappend trueValues $string 2497 } 2498 2499 # 2500 # showform -- emit a form field for a boolean 2501 # 2502 method showform {} { 2503 set class [get_css_class TD DIOFormHeader DIOFormHeader-$name] 2504 puts "<TR CLASS=\"$class\">" 2505 puts "<TD CLASS=\"$class\">$text:</TD>" 2506 2507 set class [get_css_class TD DIOFormField DIOFormField-$name] 2508 puts "<TD CLASS=\"$class\">" 2509 if {$readonly} { 2510 if {[boolean_value]} { 2511 puts $true 2512 } else { 2513 puts $false 2514 } 2515 } else { 2516 if {[boolean_value]} { 2517 $form default_value $name $true 2518 } else { 2519 $form default_value $name $false 2520 } 2521 eval $form radiobuttons $name \ 2522 -values [list "$true $false"] $formargs 2523 } 2524 puts "</TD>" 2525 puts "</TR>" 2526 } 2527 2528 # 2529 # showview -- emit a view for a boolean 2530 # 2531 method showview {{alt ""}} { 2532 set class [get_css_class TR DIOViewRow$alt DIOViewRow$alt-$name] 2533 puts "<TR CLASS=\"$class\">" 2534 2535 set class [get_css_class TD DIOViewHeader DIOViewHeader-$name] 2536 puts "<TD CLASS=\"$class\">$text:</TD>" 2537 2538 set class [get_css_class TD DIOViewField DIOViewField-$name] 2539 puts "<TD CLASS=\"$class\">" 2540 if {[boolean_value]} { 2541 puts $true 2542 } else { 2543 puts $false 2544 } 2545 puts "</TD>" 2546 2547 puts "</TR>" 2548 } 2549 2550 # 2551 # boolean_value -- return 1 if value is found in the values list, else 0 2552 # 2553 method boolean_value {} { 2554 set val [string tolower $value] 2555 if {[lsearch -exact $values $val] >= 0} { return 1 } 2556 return 0 2557 } 2558 2559 method value {{string ""}} { configvar value $string } 2560 2561 public variable true "Yes" 2562 public variable false "No" 2563 public variable values "1 y yes t true on" 2564 2565 public variable value "" { 2566 if {[boolean_value]} { 2567 set value $true 2568 } else { 2569 set value $false 2570 } 2571 } 2572 2573 method null_ok {} { 2574 return 0 2575 } 2576 2577} ; ## ::itcl::class ::STDisplayField_boolean 2578 2579package provide st_display 1.13.12 2580 2581