Index: openacs-4/packages/xotcl-core/tcl/generic-procs.tcl =================================================================== RCS file: /usr/local/cvsroot/openacs-4/packages/xotcl-core/tcl/generic-procs.tcl,v diff -u -r1.94 -r1.95 --- openacs-4/packages/xotcl-core/tcl/generic-procs.tcl 27 Oct 2009 11:34:44 -0000 1.94 +++ openacs-4/packages/xotcl-core/tcl/generic-procs.tcl 27 Oct 2014 16:42:01 -0000 1.95 @@ -1,5 +1,6 @@ ad_library { - A simple OO interface for ad_form for content repository items. + A simple OO interface for ad_form for + acs_objects and content repository items. @author Gustaf Neumann @creation-date 2005-08-13 @@ -10,10 +11,10 @@ # # Form template class # - ### FIXME: form should get a package id as parameter Class Form -parameter { fields data + {package_id ""} {folder_id -100} {name {[namespace tail [self]]}} add_page_title @@ -24,32 +25,44 @@ {submit_link "."} {action "[::xo::cc url]"} } -ad_doc { - Class for the simplified generation of forms. This class was designed +

Class for the simplified generation of forms. This class was designed together with the content repository class - ::xo::db::CrClass. - + ::xo::db::CrClass, + but it can be used also with different classes. The only requirement is the + presence of an 'item_id' form field. +

+

+ For generic acs_objects, 'item_id' will correspond to 'object_id' column in 'acs_objects' + table. For content repository items, 'item_id' will be the column by the same name in + cr_revisions/cr_items. +

} Form instproc init {} { set level [template::adp_level] my forward var uplevel #$level set - my instvar data folder_id - set package_id [$data package_id] - set folder_id [expr {[$data exists parent_id] ? [$data parent_id] : [$package_id folder_id]}] - set class [$data info class] + my instvar data package_id folder_id + if {$package_id eq ""} {set package_id [$data package_id]} + if {$folder_id < 0} { + set folder_id [expr {[$data exists parent_id] ? [$data parent_id] : [$package_id folder_id]}] + } + + set class [$data info class] + my set data_id [$class id_column] if {![my exists add_page_title]} { my set add_page_title [_ xotcl-core.create_new_type \ @@ -60,12 +73,13 @@ [list type [$class pretty_name]]] } - # check, if the specified fields are available from the data source - # and ignore the unavailable entries - set checked_fields [list] - set available_atts [$class array names db_slot] - #my log "-- available atts <$available_atts>" - lappend available_atts [$class id_column] item_id +# It's a local variable that nobody uses... commented in doubt +# # check, if the specified fields are available from the data source +# # and ignore the unavailable entries +# set checked_fields [list] +# set available_atts [$class array names db_slot] +# #my log "-- available atts <$available_atts>" +# lappend available_atts [$class id_column] item_id if {![my exists fields]} {my mkFields} #my log --fields=[my fields] @@ -81,35 +95,27 @@ Form instproc new_data {} { my instvar data #my log "--- new_data ---" - foreach __var [my form_vars] { - $data set $__var [my var $__var] - } - $data initialize_loaded_object $data save_new - return [$data set item_id] + return [$data set object_id] } Form instproc edit_data {} { #my log "--- edit_data --- setting form vars=[my form_vars]" my instvar data - foreach __var [my form_vars] { - $data set $__var [my var $__var] - } - $data initialize_loaded_object - db_transaction { - $data save + $data save + # Renaming is meant for cr_items and such + if {[$data info commands rename] ne ""} { set old_name [::xo::cc form_parameter __object_name ""] set new_name [$data set name] if {$old_name ne $new_name} { - #my msg "rename from $old_name to $new_name" - $data rename -old_name $old_name -new_name $new_name + #my msg "rename from $old_name to $new_name" + $data rename -old_name $old_name -new_name $new_name } } - return [$data set item_id] + return [$data set object_id] } Form instproc request {privilege} { - my instvar edit_form_page_title context data - set package_id [$data package_id] + my instvar edit_form_page_title context data package_id if {[my isobject ::$package_id] && ![::$package_id exists policy]} { # not needed, if governed by a policy @@ -119,18 +125,20 @@ -privilege $privilege } set edit_form_page_title [if {$privilege eq "create"} \ - {my add_page_title} {my edit_page_title}] + {my add_page_title} {my edit_page_title}] set context [list $edit_form_page_title] } - + Form instproc set_form_data {} { - my instvar data - foreach var [[$data info class] array names db_slot] { - if {[$data exists $var]} { - my var $var [list [$data set $var]] - } + my instvar data data_id + foreach var [$data info vars] { + my var $var [list [$data set $var]] } + # Alias object_id to the id of our object + if {[$data exists $data_id]} { + $data set object_id [$data set $data_id] + } } Form instproc new_request {} { @@ -145,10 +153,15 @@ } Form instproc on_submit {item_id} { - # The content of this proc is strictly speaking not necessary. - # However, on redirects after a submit to the same page, it - # ensures the setting of edit_form_page_title and context + # On redirects after a submit to the same page, ensure + # the setting of edit_form_page_title and context my request write + # Put form content into data object + my instvar data + foreach __var [my form_vars] { + $data set $__var [my var $__var] + } + $data initialize_loaded_object } Form instproc on_validation_error {} { @@ -163,36 +176,38 @@ if {$link eq "view"} { set link [export_vars -base $link {item_id}] } - #ns_log notice "-- redirect to $link // [string match *\?* $link]" + #ns_log notice "-- redirect to $link // [string match "*\?*" $link]" ad_returnredirect $link ad_script_abort } - + Form ad_instproc generate { {-template "formTemplate"} + {-mode "edit"} {-export} } { - the method generate is used to actually generate the form template + The method generate is used to actually generate the form template from the specifications and to set up page_title and context when appropriate. - @template is the name of the tcl variable to contain the filled in template - @export list of attribue value pairs to be exported to the form (nested list) + @param template is the name of the tcl variable to contain the filled in template + @param export list of attribue value pairs to be exported to the form (nested list) } { # set form name for adp file my set $template [my name] - my instvar data folder_id + my instvar data package_id folder_id set object_type [[$data info class] object_type] - if {[catch {set object_name [$data set name]}]} {set object_name ""} + set object_name [expr {[$data exists name] ? [$data set name] : ""}] #my log "-- $data, cl=[$data info class] [[$data info class] object_type]" #my log "--e [my name] final fields [my fields]" - set exports [list [list object_type $object_type] \ - [list folder_id $folder_id] \ - [list __object_name $object_name]] + set exports [list \ + [list object_type $object_type] \ + [list folder_id $folder_id] \ + [list __object_name $object_name]] if {[info exists export]} {foreach pair $export {lappend exports $pair}} - ad_form -name [my name] -form [my fields] \ + ad_form -name [my name] -form [my fields] -mode $mode \ -export $exports -action [my action] -html [my html] set new_data "set item_id \[[self] new_data\]" @@ -206,21 +221,20 @@ if {[my with_categories]} { set coid [expr {[$data exists item_id] ? [$data set item_id] : ""}] category::ad_form::add_widgets -form_name [my name] \ - -container_object_id [$data package_id] \ + -container_object_id $package_id \ -categorized_object_id $coid - + + # When editing, fill category form widgets + # with current mappings for this object + append edit_request { + category::ad_form::fill_widgets \ + -container_object_id $package_id \ + -categorized_object_id $item_id + } append new_data { category::map_object -remove_old -object_id $item_id $category_ids - #ns_log notice "-- new data category::map_object -remove_old -object_id $item_id $category_ids" - #db_dml [my qn insert_asc_named_object] \ - # "insert into acs_named_objects (object_id,object_name,package_id) \ - # values (:item_id, :name, :package_id)" } append edit_data { - #db_dml [my qn update_asc_named_object] \ - # "update acs_named_objects set object_name = :name, \ - # package_id = :package_id where object_id = :item_id" - #ns_log notice "-- edit data category::map_object -remove_old -object_id $item_id $category_ids" category::map_object -remove_old -object_id $item_id $category_ids } append on_submit { @@ -231,14 +245,374 @@ #ns_log notice "-- ad_form new_data=<$new_data> edit_data=<$edit_data> edit_request=<$edit_request>" # action blocks must be added last + # -new_data and -edit_data are enclosed in a transaction only in the end, + # so eventual additional code from category management is executed safely ad_form -extend -name [my name] \ -validate [my validate] \ - -new_data $new_data -edit_data $edit_data -on_submit $on_submit \ - -new_request $new_request -edit_request $edit_request \ + -new_data "xo::dc transaction \{ $new_data \}" -edit_data "xo::dc transaction \{ $edit_data \}" \ + -on_submit $on_submit -new_request $new_request -edit_request $edit_request \ -on_validation_error $on_validation_error -after_submit $after_submit } + + # + # List template class + # + Class List -parameter { + {actions ""} + {name {[namespace tail [self]]}} + {bulk_actions ""} + {bulk_action_method "post"} + {bulk_action_export_vars ""} + elements + {filters ""} + {formats ""} + {selected_format ""} + {rows_per_page 30} + {page 1} + {orderby ""} + {page_groupsize 10} + {row_code ""} + class + {create_url ""} + {edit_url ""} + {delete_url ""} + {no_create_p f} + {no_edit_p f} + {no_delete_p f} + {package_id ""} + {ulevel 1} + {pass_properties ""} + {checkbox_name ""} + {orderby_name ""} + {row_pretty_plural ""} + {no_data ""} + {html_main_class ""} + {html_sub_class ""} + {html_class ""} + {html ""} + {caption ""} + {bulk_action_click_function ""} + } -ad_doc { + + Simple OO interface to template::list. + This class has been built to allow quick creation of list UIs for generic acs_objects.
+
+ Many parameters are homonimous to those for template::list::create
+ and work in the same way, unless stated differently in this documentation.
+ Despite the high number of object's members, most of them are there for backward compatibility with the procedural API + and they seldom need to be specified.
+
+ An example of instantiation could just look as this:
+
+      # Must be an existing acs_object class on the system.
+      set class "::dev::Location"
+      
+      # As we are talking about acs_objects, our 'delete'
+      # page could of course be the same for every object
+      # in the system.
+      ::Generic::List create list1 \
+	  -class $class \
+	  -package_id $package_id \
+	  -rows_per_page $rows_per_page \
+	  -delete_url "../delete" \
+	  -elements {
+	      name {
+		  label "Name"
+	      }
+	      street {
+		  label "Street"
+	      }
+	      number {
+		  label "Number"
+	      }
+	      city {
+		  label "City"
+	      }
+	      region {
+		  label "Region"
+	      }
+	      country {
+		  label "Country"
+	      }
+	      coords {
+		  label "Coordinates"
+	      }
+	  } -orderby {
+	      default_value name
+	      name {
+		  label "Name"
+		  orderby_desc "name desc"
+		  orderby_asc "name asc"
+	      }
+	  } -row_code {
+	      set coords "$latitude $longitude"
+	  }
+	  
+      list1 generate
+      
+ ...while the ADP template would include this: + +
+	<listtemplate name="list1"></listtemplate>
+      
+ + Notice that in this case we didn't have to specify queries, nor populate any multirow by hand: + They have come directly from class's data-model. A list built in this way will be paginated automatically. + + @parameter actions behaves as in template::list::create. If missing, + can be automatically generated acting on create_url and no_create_p parameters (see below). + + @param bulk_action_method behaves as in template::list::create, but will + default to POST method, as it is safer with respect to possible high number of query parameters. + + @param elements behaves as in template::list::create. It must be possible + to build every element either through class's instance members, or programmatically (see row_code below). + + @rows_per_page behaves as template::list::create's page_size + parameter. Pagination is automatical for this class. To turn it off, just set this parameter to "" + + @param row_code is a script that will be executed for every row in list's multirow. As multirows are not manually specified for this + class, this is the way to build columns outside class's data-model programmatically. + + @param class is the class (descendant of acs_object) for which this list will be built. + + @param no_create_p tells to the list we don't want instance creation action button to be built automatically. + + @param create_url when instance creation url is automatically built, tells the list to which url make it point. + + @param no_edit_p tells to the list we don't want instance edit action button to be built automatically. + + @param edit_url when instance edit url is automatically built, tells the list to which url make it point. Page pointed must accept + an item_id parameter, that will be the primary key of edited instance. + + @param no_delete_p tells to the list we don't want instance delete action button to be built automatically. + + @param delete_url when instance delete url is automatically built, tells the list to which url make it point. Page pointed must accept + an item_id parameter, that will be the primary key of deleted instance. + + @param package_id is the package for this instance. It has no use for now. + + @param html_class behaves as class parameter in template::list::create. + + @param html_main_class behaves as main_class parameter in template::list::create. + + @param html_sub_class behaves as sub_class parameter in template::list::create. + + @author Antonio Pisano (antonio@elettrotecnica.it) + + } + + List instproc init {} { + my instvar class name + my set id_column [$class id_column] + my set pretty_name [$class pretty_name] + set pretty_plural [$class pretty_plural] + my set pretty_plural $pretty_plural + my set list_name $name + } + + List instproc get_actions {} { + my instvar actions no_create_p create_url + if {[string is false $no_create_p]} { + set type [my set pretty_name] + if {$create_url eq ""} {set create_url add-edit} + set create_action [list \ + [_ xotcl-core.create_new_type] $create_url [_ xotcl-core.create_new_type]] + set actions [concat $create_action $actions] + } + return $actions + } + + List instproc get_elements {} { + my instvar no_edit_p no_delete_p + set elements {} + if {!$no_edit_p} { + set type [my set pretty_name] + set title [_ xotcl-core.edit_type] + lappend elements \ + edit [list \ + link_url_col edit_url \ + display_template [list ] \ + link_html [list title $title] \ + sub_class narrow] + } + set elements [concat $elements [my set elements]] + if {!$no_delete_p} { + set title [_ xotcl-core.delete_item] + set confirm "[_ acs-subsite.Delete]?" + lappend elements \ + delete [list \ + link_url_col delete_url \ + link_html [list title $title onClick "return(confirm('${confirm}'));"] \ + display_template [list ] \ + sub_class narrow] + } + return $elements + } + + List instproc page_query {} { + my instvar class id_column list_name orderby + if {$orderby ne ""} { + return [$class instance_select_query \ + -select_attributes [list $id_column] \ + -where_clause "\[template::list::filter_where_clauses -name $list_name -and\]" \ + -orderby "\[lrange \[template::list::orderby_clause -name $list_name -orderby\] 2 end\]"] + } else { + return [$class instance_select_query \ + -select_attributes [list $id_column] \ + -where_clause "\[template::list::filter_where_clauses -name $list_name -and\]"] + } + } + + List instproc get_filters {} { + my instvar filters rows_per_page + if {$rows_per_page ne "" && + "rows_per_page" ni $filters} { + set opts {} + set opt [expr {int($rows_per_page / 2)}] + for {set i 0} {$i < 3} {incr i} { + lappend opts [list $opt $opt] + set opt [expr {$opt*($i+2)}] + } + append filters " + rows_per_page { + label \"[_ acs-templating.Page_Size]\" + values {$opts} + where_clause {1 = 1} + default_value $rows_per_page + }" + } + return $filters + } + + List instproc extend_cols {} { + set cols {} + set specs {} + foreach {el spec} [my get_elements] { + lappend cols $el + foreach {prop val} $spec { + if {$prop in + {display_col + link_url_col}} { + lappend cols $val} + }}; return $cols + } + + List instproc get_ids {} { + my instvar list_name rows_per_page + if {$rows_per_page ne ""} { + return [template::list::page_get_ids -name $list_name -tcl_list] + } + # If we are not paginating, just get all ids in table + return [::xo::dc list query [subst [my page_query]]] + } + + List instproc multirow {} { + my instvar list_name id_column no_edit_p {edit_url base_edit_url} no_delete_p {delete_url base_delete_url} row_code + if {$base_edit_url eq ""} {set base_edit_url "add-edit"} + if {$base_delete_url eq ""} {set base_delete_url "delete"} + set this_url [::xo::cc url] + set extend_cols [my extend_cols] + # Create the multirow + {*}"template::multirow create $list_name $extend_cols" + set multirow_append "template::multirow append $list_name" + foreach col $extend_cols {lappend multirow_append "\$$col"} + # Loop through objects in this page... + foreach item_id [my get_ids] { + # ...get the object + set o [::xo::db::Class get_instance_from_db -id $item_id] + {*}"$o instvar [$o info vars]" + foreach col $extend_cols {if {![info exists $col]} {set $col ""}} + set item_id [set $id_column] + if {!$no_edit_p} { + set edit_url [export_vars -base $base_edit_url {item_id}] + } + if {!$no_delete_p} { + set delete_url [export_vars -base $base_delete_url {item_id {return_url $this_url}}] + } + if {$row_code ne ""} { + # This will often be multiline, with comments etc... + # need to use the full eval command + eval $row_code + } + {*}[subst $multirow_append] + # Need to clear the area... + {*}"unset $extend_cols" + } + } + + List instproc generate {} { + my instvar list_name id_column rows_per_page bulk_actions formats ulevel + set cmd [list \ + template::list::create \ + -ulevel [expr {$ulevel+1}] \ + -name $list_name \ + -multirow $list_name \ + -actions [my get_actions] \ + -elements [my get_elements] \ + -filters [my get_filters] \ + -orderby [my set orderby]] + if {$bulk_actions ne ""} { + lappend cmd \ + -bulk_actions $bulk_actions \ + -bulk_action_method [my set bulk_action_method] \ + -bulk_action_export_vars [my set bulk_action_export_vars] \ + -key $id_column + } + if {$formats ne ""} { + lappend cmd \ + -formats $formats \ + -selected_format [my set selected_format] + } + if {$rows_per_page ne ""} { + lappend cmd \ + -page_flush_p t \ + -page_size $rows_per_page \ + -page_groupsize [my set page_groupsize] \ + -page_query [my page_query] + } + # This properties will be passed as they are + foreach prop { + pass_properties + checkbox_name + orderby_name + row_pretty_plural + no_data + caption + bulk_action_click_function + html + } { + set val [my set $prop] + if {$val ne ""} { + lappend cmd -${prop} $val + } + } + foreach prop { + html_main_class + html_sub_class + html_class + } { + set val [my set $prop] + set prop [string range $prop 5 end] + if {$val ne ""} { + lappend cmd -${prop} $val + } + } + {*}$cmd + my multirow + } + + List instproc to_csv {} { + template::list::write_csv -name [my set list_name] + } } namespace import -force ::Generic::* +# +# Local variables: +# mode: tcl +# tcl-indent-level: 2 +# indent-tabs-mode: nil +# End: